1. 程式人生 > >JAVA 與 MyCat(3) 關於getResourceAsStream(fileName)的檔案位置

JAVA 與 MyCat(3) 關於getResourceAsStream(fileName)的檔案位置

通過mycat來學習java了^^。

前一篇文章介紹了單例模式建立了MycatServer單例項,下面一起看看MycatServer初始化方法的內容:

private MycatServer() {

		// 讀取檔案配置
		this.config = new MycatConfig();

		// 定時執行緒池,單執行緒執行緒池
		scheduler = Executors.newSingleThreadScheduledExecutor();

		// SQL記錄器
		this.sqlRecorder = new SQLRecorder(config.getSystem().getSqlRecordCount());

		/**
		 * 是否線上,MyCat manager中有命令控制 | offline | Change MyCat status to OFF | |
		 * online | Change MyCat status to ON |
		 */
		this.isOnline = new AtomicBoolean(true);

		// 快取服務初始化
		cacheService = new CacheService();

		// 路由計算初始化
		routerService = new RouteService(cacheService);

		// load datanode active index from properties
		dnIndexProperties = loadDnIndexProps();
		try {
			// SQL解析器
			sqlInterceptor = (SQLInterceptor) Class.forName(config.getSystem().getSqlInterceptor()).newInstance();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}

		// catlet載入器
		catletClassLoader = new DynaClassLoader(SystemConfig.getHomePath() + File.separator + "catlet",
				config.getSystem().getCatletClassCheckSeconds());

		// 記錄啟動時間
		this.startupTime = TimeUtil.currentTimeMillis();
		if (isUseZkSwitch()) {
			String path = ZKUtils.getZKBasePath() + "lock/dnindex.lock";
			dnindexLock = new InterProcessMutex(ZKUtils.getConnection(), path);
		}
}
還是挑重點,一行行看看:
this.config = new MycatConfig();
初始化了MycatConfig的一個例項,Ctrl+左擊看看初始化都執行了什麼:
	public MycatConfig() {
		
		//讀取schema.xml,rule.xml和server.xml
		ConfigInitializer confInit = new ConfigInitializer(true);
		this.system = confInit.getSystem();
		this.users = confInit.getUsers();
		this.schemas = confInit.getSchemas();
		this.dataHosts = confInit.getDataHosts();

		this.dataNodes = confInit.getDataNodes();
		for (PhysicalDBPool dbPool : dataHosts.values()) {
			dbPool.setSchemas(getDataNodeSchemasOfDataHost(dbPool.getHostName()));
		}
		
		this.firewall = confInit.getFirewall();
		this.cluster = confInit.getCluster();
		
		//初始化重載入配置時間
		this.reloadTime = TimeUtil.currentTimeMillis();
		this.rollbackTime = -1L;
		this.status = RELOAD;
		
		//配置載入鎖
		this.lock = new ReentrantLock();
	}
看看第一行:
ConfigInitializer confInit = new ConfigInitializer(true);
看看ConfigInitializer的初始化方法都有什麼:
public ConfigInitializer(boolean loadDataHost) {

		// 讀取rule.xml和schema.xml
		SchemaLoader schemaLoader = new XMLSchemaLoader();

		// 讀取server.xml
		XMLConfigLoader configLoader = new XMLConfigLoader(schemaLoader);

		schemaLoader = null;

		// 載入配置
		this.system = configLoader.getSystemConfig();
		this.users = configLoader.getUserConfigs();
		this.schemas = configLoader.getSchemaConfigs();

		// 是否重新載入DataHost和對應的DataNode
		if (loadDataHost) {
			this.dataHosts = initDataHosts(configLoader);
			this.dataNodes = initDataNodes(configLoader);
		}

		// 許可權管理
		this.firewall = configLoader.getFirewallConfig();
		this.cluster = initCobarCluster(configLoader);

		// 不同型別的全域性序列處理器的配置載入
		if (system.getSequnceHandlerType() == SystemConfig.SEQUENCEHANDLER_MYSQLDB) {
			IncrSequenceMySQLHandler.getInstance().load();
		}

		if (system.getSequnceHandlerType() == SystemConfig.SEQUENCEHANDLER_LOCAL_TIME) {
			IncrSequenceTimeHandler.getInstance().load();
		}

		if (system.getSequnceHandlerType() == SystemConfig.SEQUENCEHANDLER_ZK_DISTRIBUTED) {
			DistributedSequenceHandler.getInstance(system).load();
		}

		if (system.getSequnceHandlerType() == SystemConfig.SEQUENCEHANDLER_ZK_GLOBAL_INCREMENT) {
			IncrSequenceZKHandler.getInstance().load();
		}

		/**
		 * 配置檔案初始化, 自檢
		 */
		// 檢查user與schema配置對應以及schema配置不為空
		this.selfChecking0();
}
看看第一行:
SchemaLoader schemaLoader = new XMLSchemaLoader();

看看XMLSchemaLoader的初始化方法有什麼:

public XMLSchemaLoader() {
		this(null, null);
}
public XMLSchemaLoader(String schemaFile, String ruleFile) {
		// 先讀取rule.xml
		XMLRuleLoader ruleLoader = new XMLRuleLoader(ruleFile);
		// 將tableRules拿出,用於這裡載入Schema做rule有效判斷,以及之後的分片路由計算
		this.tableRules = ruleLoader.getTableRules();
		// 釋放ruleLoader
		ruleLoader = null;
		this.dataHosts = new HashMap<String, DataHostConfig>();
		this.dataNodes = new HashMap<String, DataNodeConfig>();
		this.schemas = new HashMap<String, SchemaConfig>();
		// 讀取載入schema配置
		this.load(DEFAULT_DTD, schemaFile == null ? DEFAULT_XML : schemaFile);
}

看看第一行:

XMLRuleLoader ruleLoader = new XMLRuleLoader(ruleFile);
看看XMLRuleLoader(ruleFile)方法裡有什麼,這裡ruleFile為null:
public XMLRuleLoader(String ruleFile) {
		// this.rules = new HashSet<RuleConfig>();
		// rule名 -> rule
		this.tableRules = new HashMap<String, TableRuleConfig>();
		// function名 -> 具體分片演算法
		this.functions = new HashMap<String, AbstractPartitionAlgorithm>();
		// 預設為:/rule.dtd和/rule.xml
		load(DEFAULT_DTD, ruleFile == null ? DEFAULT_XML : ruleFile);
}

第一行初始化了一個HashMap型別全域性變數,存放分片規則;第二行也初始化了一個HashMap型別全域性變數,存放分片函式;看看第三行:
load(DEFAULT_DTD, ruleFile == null ? DEFAULT_XML : ruleFile);

該方法的第一個引數值為:/rule.dtd,第二個引數值為:/rule.xml,看看該方法的內容:
private void load(String dtdFile, String xmlFile) {
		InputStream dtd = null;
		InputStream xml = null;
		try {
			dtd = XMLRuleLoader.class.getResourceAsStream(dtdFile);
			xml = XMLRuleLoader.class.getResourceAsStream(xmlFile);
			// 讀取出語意樹
			Element root = ConfigUtil.getDocument(dtd, xml).getDocumentElement();
			// 載入Function
			loadFunctions(root);
			// 載入TableRule
			loadTableRules(root);
		} catch (ConfigException e) {
			throw e;
		} catch (Exception e) {
			throw new ConfigException(e);
		} finally {
			if (dtd != null) {
				try {
					dtd.close();
				} catch (IOException e) {
				}
			}
			if (xml != null) {
				try {
					xml.close();
				} catch (IOException e) {
				}
			}
		}
}

這裡有一個java知識點:

XMLRuleLoader.class.getResourceAsStream(dtdFile);
這裡的dtdFile值為/rule.dtd,那麼這個檔案在OS的檔案系統哪呢?

關於Class.getResourceAsStream(fileName)的檔案位置

1.首先fileName都是相對於classpath來說的,也就是fileName一定在classpath目錄下的某個地方。

2.如果fileName是以'/'開頭的,就是指classpath目錄。有的文章說是類的根目錄,對但不全對,因為可以是classpath中任一目錄。

3.如果fileName不是以'/'開頭的,則是指類所在目錄。

例如有如下目錄結構:


假如classpath為/u01/mycat/lib/,那麼:

Test.class.getResourceAsStream('conf.xml');就是指/u01/mycat/lib/io/mycat/conf.xml檔案。

Test.class.getResourceAsStream('/io/mycat/conf.xml');也是指/u01/mycat/lib/io/mycat/conf.xml檔案。

Test.class.getResourceAsStream('/conf.xml');就是指/u01/mycat/lib/conf.xml檔案。

一般我們都將配置檔案放在conf目錄下,而不放在lib目錄下,那麼只要在classpath裡再多指定一個conf目錄即可,JVM會搜尋所有classpath指定的目錄。

我們知道,classpath可以是目錄,也可以是jar包,JVM會自動將jar包解壓成目錄,所以conf.xml是在目錄裡,還是在jar包裡都可以。

說個題外話,classpath指定jar包時,需要一個一個指定,即使所有的jar包都在同一個目錄,也不能使用萬用字元,如果有一大堆jar包需要指定比較煩,其實有個引數可以使用:-Djava.ext.dirs,例如所有jar包在/u01/mycat/lib/目錄下,可以如下指定:java -Djava.ext.dirs=/u01/mycat/lib MycatStartup,而不用再一個個地指定jar包。

除了class.getResourceAsStream(fileName)外,還可以使用ClassLoader.getResourceAsStream(fileName)方法來獲取資源。實際上Class.getResourceAsStream(fileName)方法就是委託給ClassLoader.getResourceAsStream(fileName)方法來執行的,兩者基本上沒有區別,只是在路徑指定上稍有區別。ClassLoader.getResourceAsStream(fileName)中的fileName不能以'/'開頭,檔案相對路徑永遠從classpath開始,例如:

ClassLoader.getResourceAsStream('conf.xml');就是指/u01/mycat/lib/conf.xml檔案。

ClassLoader.getResourceAsStream('io/mycat/conf.xml');就是指/u01/mycat/lib/io/mycat/conf.xml檔案。

獲取資源流的方法還有許多,總結如下:

this.getClass().getResourceAsStream("fileName"); 

this.getClass().getClassLoader().getResourceAsStream("fileName"); 

ClassLoader.getSystemResourceAsStream("fileName"); 

Thread.currentThread().getContextClassLoader().getResourceAsStream("fileName"); 

為什麼為這麼多方法呢,其實是跟類載入有關。ClassLoader是個抽象類,比如mycat裡的MyDynaClassLoader類就是ClassLoader的子類,這些子類可能會重寫與資源載入有關的方法,所以各子類的getResourceAsStream("fileName")方法獲取資源的方式可能會有所不同。不同的場合使用不同的資源獲取方法,例如上面的四個方法:

this.getClass().getResourceAsStream("fileName"); 與this.getClass().getClassLoader().getResourceAsStream("fileName"); 兩種方式都是使用本類的載入器獲取資源的,可以認為是同一種方式;

ClassLoader.getSystemResourceAsStream("fileName");這種是系統預設的類載入器,最好別用。例項上上面的兩種在本類沒有特殊指定載入器時,使用的就是這個系統預設載入器,所以使用上面各種方式就行了,不要使用這種方式。

Thread.currentThread().getContextClassLoader().getResourceAsStream("fileName");使用當前執行緒的類載入器,這種方式多用在伺服器框架上,如Tomcat、WebSphere等。程式在Tomcat裡執行時,要獲取資源,肯定是從Tomcat某個目錄找的,而不是從執行Tomcat的java程序的classpath裡的某個目錄找。