cuiyi's blog(崔毅 crazycy)

记录点滴 鉴往事之得失 以资于发展
数据加载中……

ClassLoader专题(四):部署ear包出错引发的ClassLoader的思考

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的实现类在当前的类加载器,故此发生错误。



posted on 2008-03-18 18:08 crazycy 阅读(2552) 评论(0)  编辑  收藏 所属分类: JavaSE语言


只有注册用户登录后才能发表评论。


网站导航: