ClassLoader专题(一):ClassLoader基础
ClassLoader专题(二):从Servlet容器看ClassLoader机制的妙用
ClassLoader专题(三):引文
ClassLoader专题(四):部署ear包出错引发的ClassLoader的思考
应用服务器常常包含多个容器,当前使用的是JBoss,在
部署ear包的时候,遇到了一些比较有意思的问题,遂随着不断的推敲,从而解决了问题,也对classloader在应用服务器如JBoss中有了一点的推测(不当之处请光顾的朋友指出)。
测试环境: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的实现类,后者确实不断地更新,但是始终未被执行,而执行的总是前者
2)ant打包,把${xml_config_file}放在了${ear_file}/${jar_file} 和${ear_file}/${war_file}/WEB-INF/classes/${xml_config_file}
之后做了如下的测试:
21)前者不变,更新后者,结果:取新增加的物件出错
22)移除前者,更新后者,结果:可以取到新增加的物件
23)保持前者,新物件的配置作为一个新的文件,同时也放在后者的位置,结果:可以取到新增加的物件。
3)通过IoC注入配置文件的位置,然后读取配置文件的内容(未使用Spring的解析方法,而是自己实现解析):
注入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后,交给了一个工具类来解析,这个工具类放在表现层,即打包到war里,代码类似如下
private static Document getRootDocument(String fileName) throws DocumentException{//参数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实际上并不存在;
得到的输出结果(外加了打印语句的本身描述)
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/
执行到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));
得到的输出结果(外加了打印语句的本身描述)
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)
在应用服务器如JBoss中,加载${ear_file}/${jar_file}的EJB容器 和 加载${ear_file}/${war_file}的Web容器间存在一定的关系,根据ClassLoader的加载机制:当当前类加载器需要加载一个类的时候,首先请求父级的类加载器加载,如果父级加载器无法找到要加载的类(每个加载器仅仅在自己本身的classpath寻找要加载的类),才由当前类加载器来加载,如果加载不到就报错。
根据这个,可认为EJB容器的ClassLoader起了Web容器的父级ClassLoader的作用,即:请求加载一个action class时,当前类加载器是web容器,但是web容器的ClassLoader委托其父级加载器来加载,结果其父亲加载并加载成功了,所以不再加载本来正确的${war_file}/WEB-INF/lib or ${war_file}/WEB-INF/classes下的真正的类了
对3)
这些输出信息则更充分的证明了当使用${Class_name}.class.getClassLoader()的时候,真正起作用的类加载器便是父级类加载器,即使EJB容器的ClassLoader,从而得到的当前classpath是${ear_file}的路径。
回想过去经历:
基于这些实验,记得曾经遇到这样的错误:把struts.jar也放在了${ear_file}之下,运行报错误。
原因依然是类加载器的两个基本原理:
1)加载的委托机制,见上面的分析
2)当一个类被某一个ClassLoader加载后,与其相关的类都由同一个ClassLoader加载
于是得出如下结论:EJB容器加载了struts.jar,当web容器的ClassLoader加载自己的action class的实现类的时候,需要Action基类,但是根据默认的加载原理,关联的类应该由同一个类加载器完成,现在Action基类被父级的加载器加载(相对于当前),Action的实现类在当前的类加载器,故此发生错误。