ClassLoader專題(一):ClassLoader基礎(chǔ)
ClassLoader專題(二):從Servlet容器看ClassLoader機制的妙用
ClassLoader專題(三):引文
ClassLoader專題(四):部署ear包出錯引發(fā)的ClassLoader的思考
應(yīng)用服務(wù)器常常包含多個容器,當(dāng)前使用的是JBoss,在
部署ear包的時候,遇到了一些比較有意思的問題,遂隨著不斷的推敲,從而解決了問題,也對classloader在應(yīng)用服務(wù)器如JBoss中有了一點的推測(不當(dāng)之處請光顧的朋友指出)。
測試環(huán)境:JBoss4.0.5.GA 、Gentoo Linux、 spring、ejb(
ear工程)
1)使用ant打包腳本的疏忽,把struts action的class同時放在了${ear_file}/${jar_file} 和${ear_file}/${war_file}/WEB-INF/{lib}/${jar_file}
糾正之后,再次修改了struts action的實現(xiàn)類,后者確實不斷地更新,但是始終未被執(zhí)行,而執(zhí)行的總是前者
2)ant打包,把${xml_config_file}放在了${ear_file}/${jar_file} 和${ear_file}/${war_file}/WEB-INF/classes/${xml_config_file}
之后做了如下的測試:
21)前者不變,更新后者,結(jié)果:取新增加的物件出錯
22)移除前者,更新后者,結(jié)果:可以取到新增加的物件
23)保持前者,新物件的配置作為一個新的文件,同時也放在后者的位置,結(jié)果:可以取到新增加的物件。
3)通過IoC注入配置文件的位置,然后讀取配置文件的內(nèi)容(未使用Spring的解析方法,而是自己實現(xiàn)解析):
注入xml位置的配置如下(粗體處):
<bean id="test.DataMigrateCenter" class="demo.service.DataMigrateCenter">
<property name="dataExtractDao"><ref bean="demo.dataExtractDao"/></property>
<property name="markExtractedDao"><ref bean="demo.markExtractedDao"/></property>
<property name="errorsPath" value="/home/cuiyi/demo/Errors/"/>
<property name="invoicesPath" value="/home/cuiyi/demo/Invoices/"/>
<property name="archivesPath" value="/home/cuiyi/demo/Archives/"/>
<property name="sqlPath" value="x.war/WEB-INF/classes/xyz_sql.xml"/>
</bean>
xyz_sql.xml的真實位置在/home/jboss/jboss-4.0.5.GA/server/xyz/deploy/x.ear/x.war/WEB-INF/classes/xyz_sql.xml
注入了sqlPath后,交給了一個工具類來解析,這個工具類放在表現(xiàn)層,即打包到war里,代碼類似如下
private static Document getRootDocument(String fileName) throws DocumentException{//參數(shù)fileName即注入的sqlPath
SAXReader reader = new SAXReader();
//Print Code
InputStream in = SqlReaderHelper.class.getClassLoader().getResourceAsStream(fileName);
Document document = reader.read(in);
return document;
}
在getRootDocument方法的Print Code處,增加如下打印語句:
System.out.println(SqlReaderHelper.class.getClassLoader().getResource("/"));
System.out.println(SqlReaderHelper.class.getClassLoader().getResource(""));
System.out.println(SqlReaderHelper.class.getClassLoader().getResource("/test.xml"));
System.out.println(SqlReaderHelper.class.getClassLoader().getResource("/../test.xml"));
System.out.println(SqlReaderHelper.class.getClassLoader().getResource("../test.xml"));
System.out.println(Thread.currentThread().getContextClassLoader().getResource("/"));
System.out.println(Thread.currentThread().getContextClassLoader().getResource(""));
System.out.println(SqlReaderHelper.class.getClass().getClassLoader().getResource(""));
其中,使用的test.xml實際上并不存在;
得到的輸出結(jié)果(外加了打印語句的本身描述)
17:47:18,213 INFO [STDOUT] -------->>>>>>>>SqlReaderHelper.class.getClassLoader().getResource("/") : null
17:47:18,214 INFO [STDOUT] -------->>>>>>>>SqlReaderHelper.class.getClassLoader().getResource("") : file:/home/jboss/jboss-4.0.5.GA/server/xyz/deploy/x.ear/
17:47:18,222 INFO [STDOUT] -------->>>>>>>>SqlReaderHelper.class.getClassLoader().getResource("/test.xml") : null
17:47:18,231 INFO [STDOUT] -------->>>>>>>>SqlReaderHelper.class.getClassLoader().getResource("/../test.xml") : null
17:47:18,241 INFO [STDOUT] -------->>>>>>>>SqlReaderHelper.class.getClassLoader().getResource("../test.xml") : null
17:47:18,241 INFO [STDOUT]-------->>>>>>>>Thread.currentThread().getContextClassLoader().getResource("/"):null
17:47:18,242 INFO [STDOUT]-------->>>>>>>>Thread.currentThread().getContextClassLoader().getResource(""):file:/home/jboss/jboss-4.0.5.GA/server/xyz/deploy/x.ear/
執(zhí)行到System.out.println(Thread.currentThread().getContextClassLoader().getResource("")); 出錯
將test.xml換成一個真實存在的文件 test.jar
并在getRootDocument方法的Print Code處,增加如下打印語句:
System.out.println(SqlReaderHelper.class.getClassLoader().getResource("/"));
System.out.println(SqlReaderHelper.class.getClassLoader().getResource(""));
System.out.println(SqlReaderHelper.class.getClassLoader().getResource("/test.jar"));
System.out.println(SqlReaderHelper.class.getClassLoader().getResource("/../test.jar"));
System.out.println(SqlReaderHelper.class.getClassLoader().getResource("../test.jar"));
System.out.println(SqlReaderHelper.class.getClassLoader().getResource("test.jar"));
System.out.println(Thread.currentThread().getContextClassLoader().getResource("/"));
System.out.println(Thread.currentThread().getContextClassLoader().getResource(""));
System.out.println(SqlReaderHelper.class.getClassLoader().getResource(fileName));
得到的輸出結(jié)果(外加了打印語句的本身描述)
17:57:16,882 INFO [STDOUT] -------->>>>>>>SqlReaderHelper.class.getClassLoader().getResource("/") : null
17:57:16,900 INFO [STDOUT] -------->>>>>>>SqlReaderHelper.class.getClassLoader().getResource("") : file:/home/jboss/jboss-4.0.5.GA/server/xyz/deploy/x.ear/
17:57:16,909 INFO [STDOUT] -------->>>>>>>SqlReaderHelper.class.getClassLoader().getResource("/test.jar") : null
17:57:16,918 INFO [STDOUT] -------->>>>>>>SqlReaderHelper.class.getClassLoader().getResource("/../test.jar") : null
17:57:16,926 INFO [STDOUT] -------->>>>>>>SqlReaderHelper.class.getClassLoader().getResource("../test.jar") : null
17:57:16,926 INFO [STDOUT] -------->>>>>>>SqlReaderHelper.class.getClassLoader().getResource("test.jar") : file:/home/jboss/jboss-4.0.5.GA/server/xyz/deploy/x.ear/test.jar
17:57:16,927 INFO [STDOUT]-------->>>>>>>Thread.currentThread().getContextClassLoader().getResource("/"):null
17:57:16,927 INFO [STDOUT]------->>>>>>>Thread.currentThread().getContextClassLoader().getResource(""):file:/home/jboss/jboss-4.0.5.GA/server/xyz/deploy/x.ear/
17:57:16,927 INFO [STDOUT] -------->>>>>>>SqlReaderHelper.class.getClassLoader().getResource("fileName") : file:/home/jboss/jboss-4.0.5.GA/server/xyz/deploy/x.ear/cxc3.war/WEB-INF/classes/cxc2sap_sql.xml
通過上述描述,可以簡單的得出一些推論:
對1)2)
在應(yīng)用服務(wù)器如JBoss中,加載${ear_file}/${jar_file}的EJB容器 和 加載${ear_file}/${war_file}的Web容器間存在一定的關(guān)系,根據(jù)ClassLoader的加載機制:當(dāng)當(dāng)前類加載器需要加載一個類的時候,首先請求父級的類加載器加載,如果父級加載器無法找到要加載的類(每個加載器僅僅在自己本身的classpath尋找要加載的類),才由當(dāng)前類加載器來加載,如果加載不到就報錯。
根據(jù)這個,可認為EJB容器的ClassLoader起了Web容器的父級ClassLoader的作用,即:請求加載一個action class時,當(dāng)前類加載器是web容器,但是web容器的ClassLoader委托其父級加載器來加載,結(jié)果其父親加載并加載成功了,所以不再加載本來正確的${war_file}/WEB-INF/lib or ${war_file}/WEB-INF/classes下的真正的類了
對3)
這些輸出信息則更充分的證明了當(dāng)使用${Class_name}.class.getClassLoader()的時候,真正起作用的類加載器便是父級類加載器,即使EJB容器的ClassLoader,從而得到的當(dāng)前classpath是${ear_file}的路徑。
回想過去經(jīng)歷:
基于這些實驗,記得曾經(jīng)遇到這樣的錯誤:把struts.jar也放在了${ear_file}之下,運行報錯誤。
原因依然是類加載器的兩個基本原理:
1)加載的委托機制,見上面的分析
2)當(dāng)一個類被某一個ClassLoader加載后,與其相關(guān)的類都由同一個ClassLoader加載
于是得出如下結(jié)論:EJB容器加載了struts.jar,當(dāng)web容器的ClassLoader加載自己的action class的實現(xiàn)類的時候,需要Action基類,但是根據(jù)默認的加載原理,關(guān)聯(lián)的類應(yīng)該由同一個類加載器完成,現(xiàn)在Action基類被父級的加載器加載(相對于當(dāng)前),Action的實現(xiàn)類在當(dāng)前的類加載器,故此發(fā)生錯誤。