The NoteBook of EricKong

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  611 Posts :: 1 Stories :: 190 Comments :: 0 Trackbacks

概述

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层次结构图
图一、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 模块中
图四、Utility模块(JAR文件)在Web 模块中

如果我们将WAR Classloader Policy设置为MODULE(默认设置),如图五所示:


图五、WAR Classloader Policy设置
图五、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文件
图八、MANIFEST.MF文件

2、 在EJB模块的MANIFEST.MF文件中选择Utility.jar


图九、EJB模块的MANIFEST.MF文件
图九、EJB模块的MANIFEST.MF文件

3、 在WEB模块的MANIFEST.MF文件中选择Utility.jar


图十、WEB模块的MANIFEST.MF文件
图十、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

posted on 2011-12-27 16:06 Eric_jiang 阅读(690) 评论(0)  编辑  收藏 所属分类: Java

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


网站导航: