部署ear包出错引发的ClassLoader的思考

本文通过对JBoss应用服务器上部署EAR包时遇到的问题进行分析,深入探讨了类加载器的工作机制及其对容器间类加载的影响,并通过实验验证了EJB容器与Web容器间的加载关系。
应用服务器常常包含多个容器,当前使用的是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/demo/Errors/"/>
<property name="invoicesPath" value="/home/demo/Invoices/"/>
<property name="archivesPath" value="/home/demo/Archives/"/>

[b]<property name="sqlPath" value="x.war/WEB-INF/classes/xyz_sql.xml"/>[/b]
</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


[color=red]通过上述描述,可以简单的得出一些推论:[/color]
[b]对1)2)[/b]
在应用服务器如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下的真正的类了

[b]对3)[/b]
这些输出信息则更充分的证明了当使用${Class_name}.class.getClassLoader()的时候,真正起作用的类加载器便是父级类加载器,即使EJB容器的ClassLoader,从而得到的当前classpath是${ear_file}的路径。

[b]回想过去经历:[/b]
基于这些实验,记得曾经遇到这样的错误:把struts.jar也放在了${ear_file}之下,运行报错误。
原因依然是类加载器的两个基本原理:
1)加载的委托机制,见上面的分析
2)当一个类被某一个ClassLoader加载后,与其相关的类都由同一个ClassLoader加载
于是得出如下结论:EJB容器加载了struts.jar,当web容器的ClassLoader加载自己的action class的实现类的时候,需要Action基类,但是根据默认的加载原理,关联的类应该由同一个类加载器完成,现在Action基类被父级的加载器加载(相对于当前),Action的实现类在当前的类加载器,故此发生错误。
### 东方通应用服务器上部署 EAR 文件的操作指南 在东方通应用服务器(TongWeb)上部署 EAR 文件需要遵循一系列配置步骤和规则,确保环境准备充分、依赖库正确加载以及部署流程顺利进行。以下是详细的说明: #### 1. 环境准备 确保已经完成以下基础配置: - 已安装符合要求的 JDK 版本(最低为 JDK 1.8)。此步骤可以通过检查系统环境变量确认[^2]。 - TongWeb 企业版已正确安装到目标服务器,并且授权文件已上传[^2]。 - 防火墙配置允许相关端口(如 8088 和 9060)的通信。 #### 2. 配置共享库优先级 在部署 EAR 文件时,需明确共享库的加载顺序与优先级。TongWeb 中涉及的共享库括以下几类: - `${TongWeb_Base}\lib\classes` - `${TongWeb_Base}\lib\common` - `assets.xml` 定义的控制台共享库 这些库的加载优先级通常由服务器的类加载器策略决定。默认情况下,EAR 应用中的 `/APP-INF/lib` 目录优先于全局共享库加载[^1]。如果需要调整优先级,可以通过修改 `server.xml` 或其他配置文件实现。例如,在 `server.xml` 中设置类加载器策略为 `parent-last` 模式,以确保应用级别的依赖优先加载。 ```xml <classloader delegate="false" /> ``` 上述配置表示关闭父类加载器优先模式,使得应用内部的类库优先加载。 #### 3. 部署 EAR 文件 将 EAR 文件部署到 TongWeb 的方法如下: 1. **通过管理控制台**: - 登录 TongWeb 管理控制台。 - 进入“应用管理”模块,选择“部署新应用”。 - 上传 EAR 文件并指定部署路径。 - 启动应用并验证其运行状态。 2. **手动部署**: - 将 EAR 文件放置到 TongWeb 的默认部署目录(如 `${TongWeb_Base}/webapps`)。 - 确保 EAR 文件解压后形成的目录结构符合规范,特别是 `/APP-INF/lib` 中的依赖库需完整。 - 启动 TongWeb 并通过日志确认应用成功加载。 #### 4. 验证部署结果 部署完成后,可通过以下方式验证: - 访问应用的入口页面或服务接口,确保功能正常。 - 检查 TongWeb 的日志文件(如 `catalina.out` 或 `server.log`),确认无错误信息。 - 如果存在类加载冲突,检查 `server.xml` 或其他配置文件中的类加载器策略是否正确。 #### 5. 注意事项 - 确保 EAR 文件中含完整的依赖库,避免因缺失依赖导致启动失败。 - 如果使用外部共享库(如 `${TongWeb_Base}\lib\common`),需明确其版本与应用内部依赖是否兼容。 - 在多应用环境下,注意避免不同应用间的类加载冲突。 --- ### 示例代码:修改类加载器策略 以下是一个示例配置,用于设置类加载器为 `parent-last` 模式: ```xml <Context> <Loader delegate="false" /> </Context> ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值