概述
Java Classloader长期以来一直是导致混乱的根源,并且随着 Java 语言的发展变得比以往任何时候都更为复杂。随着 J2EE 应用服务器的出现,Java Classloader的复杂性进一步增加了。现在,JVM 中的每个应用程序都可能有自己的Classloader层次,这将导致一个单独的 JVM 可能包含许多的Classloader。而不同的J2EE应用服务器的Classloader的层次和策略也会有所差异,移植的复杂度也随之上升。
第一部分曾经提到:在移植的前期,我们应当分析系统的框架,澄清其中各个模块之间的依赖关系(包括各个业务模块之间的依赖关系,以及Web模块和EJB模块之间的依赖关系等),为了尽量避免模块之间的交叉引用,我们甚至要重新打包。大家可能会问,我们的应用程序原本在WebLogic 、Tomcat、Jboss、Resin上能够正常的运行,为什么我们要重新分析模块之间的依赖关系,甚至重新打包呢?实际上,每个应用服务器都有着对于ClassLoader的不同实现。由于这些ClassLoader在装载类文件时的行为会有所差异,所以如果应用程序未经重整和优化,很有可能在WebSphere应用服务器上运行失败。然而,面临这种差异,我们并不需要过多的恐慌。实际上,只要把握以下几个原则,相信,交织在模块依赖和类型装载之中的问题,就可以迎刃而解了:
一、 理解WebSphere ClassLoader的装载层次和装载策略
二、 理清模块间的依赖关系
三、 将合适的装载策略应用到各个模块
注意:我们并不需要了解WebLogic、Tomcat、JBoss、Resin等J2EE应用服务器的 Classloader的体系结构,只要我们理解了WebSphere的Classloader的体系结构,理清了应用程序的各个模块之间的依赖关系,就可以解决由不同的J2EE应用服务器的Classloader的体系结构的差异给移植带来的问题。
首先我们简单介绍一下WebSphere ClassLoader的体系结构,详细内容可参看参考文档。
图一、WebSphere Classloader层次结构图
处于体系结构最上层的是系统 Classloader,它由bootstrap,extensions和 classpath三个classloader组成,bootstrap classloader会查找jre/lib下的类文件,从而加载核心类库,extensions classloader使用系统属性java.ext.dirs(通常是jre/lib/ext)查找和加载类文件,classpath classloader使用操作系统的classpath环境变量来查找和加载类文件。
处于体系结构第二层的是WebSphere Extensions Classloader,它被用于装载WebSphere的运行时类库、J2EE类库以及用户代码。它使用系统属性ws.ext.dirs(通常是%was_root%\classes,%was_root%\lib和%was_root%\lib\ext)查找和加载类文件。
处于体系结构第三层的是Application Classloader,用于加载EARs,RARs,JARs中的类文件。由于Application Classloader Policy是MULTIPLE,WebSphere应用服务器会为每一个EAR文件(EAR 1到EAR N)分配了一个Application Classloader。值得注意的是,如果WAR Classloader Policy如果设置为Application,那么Application Classloader可用于加载WARs中的类文件。
处于体系结构最底层的是WAR Classloader,用于加载WAR文件中的类文件。图中示意了WebSphere应用服务器为每一个WAR文件(WAR 1到 WAR N)分配了一个WAR Classloader的场景。值得注意的是,如果WAR Classloader Policy设置为MODULE,WebSphere应用服务器才会使用WAR Classloader加载WAR文件中的类文件。
图一中的单向箭头表明了引用次序,即处于下面的Classloader可以调用其上层的类文件,而上层的Classloader无法调用其下面的类文件。这就为应用程序及其各个模块的组织结构和设置Classloader策略提供了线索和限制。
接下来,我们用一个实例来说明针对特定的模块依赖关系应当如何设置WebSphere Classloader的装载策略(policy)以及如何将各个模块部署到合适的位置。
有一个应用程序里有三个互相依赖的模块,其调用关系如下:
图二、模块关系图
其中,WAR模块中的SampleServlet类调用了UTILITY模块中SampleUtility类,如代码一所示:
代码一、SampleServlet.java
public class SampleSevlet extends HttpServlet implements Servlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("SampleServlet invokes SampleUtility");
new SampleUtility().callSampleSessionBean();
}
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
}
}
|
UTILITY模块中SampleUtility类调用了EJB模块中的SampleSessionBean类(通过callSampleSessionBean方法),如代码二所示:
代码二、SampleUtility.java
public class SampleUtility {
public void callSampleSessionBean() {
try{
Context initCtx = new InitialContext();
Object result = initCtx.lookup("ejb/sample/SampleSessionHome");
SampleSessionHome ssh = (SampleSessionHome)
PortableRemoteObject.narrow(
result, SampleSessionHome.class);
SampleSession ss = ssh.create();
System.out.println("SampleUtility invokes SampleSessionBean");
ss.callSampleUtility();
}catch(Exception e){
e.printStackTrace();
}
}
public void finish() {
System.out.println("The process has been finished");
}
}
|
SampleSessionBean类又调用了UTILITY模块中的SampleUtility类,如代码三所示:
代码三、SampleSessionBean.java
public class SampleSessionBean implements javax.ejb.SessionBean {
private javax.ejb.SessionContext mySessionCtx;
public javax.ejb.SessionContext getSessionContext() {
return mySessionCtx;
}
public void setSessionContext(javax.ejb.SessionContext ctx) {
mySessionCtx = ctx;
}
public void ejbCreate() throws javax.ejb.CreateException {
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void ejbRemove() {
}
public void callSampleUtility() {
System.out.println("SampleSessionBean invokes SampleUtility");
new SampleUtility().finish();
}
}
|
这三个类之间的调用关系如下图所示:
图三、顺序图
下面我们通过不同的部署策略来深入探讨WebSphere Classloader是如何影响应用程序运行的。1、如果将Utility模块(JAR文件)拷贝到Web 模块的 WEB-INF/lib 文件夹中,那么,Utility模块将会被视为War模块的一部分,如图所示:
图四、Utility模块(JAR文件)在Web 模块中
如果我们将WAR Classloader Policy设置为MODULE(默认设置),如图五所示:
图五、WAR Classloader Policy设置
-
那么WebSphere应用服务器会使用WAR Classloader来装载WAR模块的类文件。而EJB模块始终是通过Application Classloader进行装载,由于Application Classloader处于WAR classloader的上层,EJB模块无法引用War模块的类文件。这样,当SampleSessionBean调用SampleUtility时,会抛出异常:
SystemOut O SampleServlet invokes SampleUtility
SystemOut O SampleUtility invokes SampleSessionBean
SystemOut O SampleSessionBean invokes SampleUtility
ExceptionUtil E CNTR0020E: 在 bean"BeanId(Sample#SampleEJB.jar#SampleSession, null)"上处理方法"callSampleUtility"时发生非应用程序异常。异常数据:
java.lang.NoClassDefFoundError: sample/SampleUtility
at sample.SampleSessionBean.callSampleUtility(SampleSessionBean.java:41)
-
如果我们将WAR Classloader Policy设置为APPLICATION,那么WebSphere应用服务器会使用Application Classloader来装载WAR模块和EJB模块的类文件。由于两个模块使用了相同的Classloader进行装载,所以两个模块间的类可以相互引用应用服务器输出的结果如下:
SystemOut O SampleServlet invokes SampleUtility
SystemOut O SampleUtility invokes SampleSessionBean
SystemOut O SampleSessionBean invokes SampleUtility
SystemOut O The process has been finished
2、如果将Utility模块拷贝到%was_root%\lib\ext , Websphere应用服务器会使用WebSphere Extensions Classloader加载Utility模块中的类文件,由于WebSphere Extensions Classloader处于Application classloader和WAR classloader的上层,所以SampleServlet和SampleSessionBean可以引用到SampleUtility,但是SampleUtility引用不到SampleSessionBean,因此,当SampleUtility调用SampleSessionBean时会抛出异常。
上面讲的配置方法比较适合将已经开发完的J2EE应用程序按照原有的包结构部署到WebSphere上,但有的项目可能会在开发完部分子系统的时候,就要将J2EE应用程序迁移到WebSphere上,然后,在WSAD中继续开发未完成的子系统。这样,把UTILITY模块打包到WAR模块的WEB-INF/lib 文件夹中,将使得开发公用类变得繁琐。幸运的是,WSAD提供了一个方式使公用类的开发和调试方法变得简单、清晰。
我们还用上面的实例进行演示。
首先,我们要为UTILITY模块创建一个JAVA项目。然后将utility.jar中的SampleUtility.java导入到此JAVA项目(页可以称作实用程序项目)当中。创建项目的结果如下图所示:
图六、实用程序项目
然后,将此实用程序项目添加到应用程序部署描述符当中,如图七所示:
1、 选择"Sample"项目中的"应用程序部署描述符";
2、 选择"模块";
3、 在"项目实用程序JAR栏目中"点击"添加";
4、 选择"Utility"项目,点击"完成"。
图七、应用程序部署描述符
在所有依赖于此模块的项目中,添加JAR模块依赖项(主要包括EJB模块和WEB模块):
1、在EJB模块和WEB模块分别编辑MANIFEST.MF文件,双击MANIFEST.MF文件可打开可视化编辑器编辑此文件
图八、MANIFEST.MF文件
2、 在EJB模块的MANIFEST.MF文件中选择Utility.jar
图九、EJB模块的MANIFEST.MF文件
3、 在WEB模块的MANIFEST.MF文件中选择Utility.jar
图十、WEB模块的MANIFEST.MF文件
接下来,我们可以启动应用服务器,对SampleServlet进行测试,控制台的显示结果如下:
SystemOut O SampleServlet invokes SampleUtility
SystemOut O SampleUtility invokes SampleSessionBean
SystemOut O SampleSessionBean invokes SampleUtility
SystemOut O The process has been finished