当前位置: 代码迷 >> Web前端 >> [通译]WebSphere Application Server V7:理解类装入器
  详细解决方案

[通译]WebSphere Application Server V7:理解类装入器

热度:848   发布时间:2012-07-25 09:43:05.0
[翻译]WebSphere Application Server V7:理解类装入器

说明:从word导入到博客,调整格式太麻烦了,我就不调整了,如果需要可以下载附件或如下地址的pdf版

http://download.csdn.net/source/3384756

?

?

WebSphere Application Server V7:理解类装入器

Java类装入器介绍 2
WebSphere类装入器概述 5
WebSphere扩展类装入器 5
应用程序和Web模块类装入器 7
处理JNI代码 8
配置WebSphere的类装入器 8
应用服务器类装入器策略 8
类加载/委托模式 11
共享库 12
类装入器查看器 13
通过实例学习类装入器 13
第一步:简单的Web模块 14
第二步:添加EJB模块和工具JAR 17
第三步:改变WAR类装入器的委托模式 18
第四步:使用共享库共享工具JAR 19
参考资料 24
说明 24

修改纪录
序号? 章节????????????? 类型?? 日期?????? 作者(翻译)????? 备注
1 所有 新建 2011-06-22 upeepu 初始版本
2

?

理解Java和WebSphere类装入器的工作方式对于打包和部署Java EE5的应用非常重要。错误地设置类装入器属性,在启动服务时很可能导致一系列臭名昭著的类加载异常(例如:ClassNotFoundException)。
在本章中,我们将解释类装入器以及如何个性化设置WebSphere的类装入器以满足应用的特殊需求。本章最后会给出一些示例来说明这些概念。

本章涉及到以下内容:
? Java类装入器介绍
? WebSphere类装入器概览
? WebSphere类装入器配置
? 类加载查看器
? 通过实例学习类装入器

Java类装入器介绍
类装入器帮助Java虚拟机(JVM)加载类。给出类的名字,类装入器会定位到这个类的定义。每个Java类都必须由类装入器加载。
当启动JVM时,你就在使用三个类装入器:引导程序(bootstrap)类装入器,扩展(extensions)类装入器和应用程序(application)类装入器:
? 引导程序类装入器负责加载Java_home/jre/lib目录下的Java核心库。这个类装入器是核心JVM的一部分,由本地代码编写。
? 扩展类装入器负责加载扩展目录中的代码(Java_home/jre/lib/ext目录或者其他由系统属性java.ext.dirs定义的目录)。该类装入器由sun.misc.Launcher$ExtClassLoader实现。
? 应用程序类装入器负责加载java.class.path目录下的代码,该目录最终映射为系统环境变量CLASSPATH。该类装入器由sun.misc.Launcher$AppClassLoader实现。

在处理类装入器时,首先需要理解父级委托模型(parent-delegation model)。它表示类装入器在加载类之前会委托父级类装入器来加载。父级类装入器可以是另一个自定义类装入器,也可以是引导程序类装入器。但重要的是,类装入器只能委托父级类装入器而不能委托子类装入器(它只能逐级向上而不能向下)。

扩展类装入器是应用程序类装入器的父级类装入器,引导程序类装入器是扩展类装入器的父级类装入器。类装入器结构见图1.

如果应用程序类装入器要加载一个类,它首先委托扩展类装入器,进而委托引导程序类装入器。如果父级类装入器不能加载该类,子类装入器尝试在自身库中加载。在这种情况下,类装入器只负责加载祖先类装入器无法加载的类。

如果一个类是有类装入器树的非叶节点类装入器加载的,这种行为会导致一些有趣的问题。
例1:类WhichClassLoader1加载类WhichClassLoader2,然后调用类WhichClassLoader3。



图1 Java类装入器结构图

例1:WhichClassLoader1 和 WhichClassLoader2 源代码

public class WhichClassLoader1 {
public static void main(String[] args) throws javax.naming.NamingException {
// Get classpath values
String bootClassPath = System.getProperty("sun.boot.class.path");
String extClassPath = System.getProperty("java.ext.dirs");
String appClassPath = System.getProperty("java.class.path");
// Print them out
System.out.println("Bootstrap classpath =" + bootClassPath + "\n");
System.out.println("Extensions classpath =" + extClassPath + "\n");
System.out.println("Application classpath=" + appClassPath + "\n");
// Load classes
Object obj = new Object();
WhichClassLoader1 wcl1 = new WhichClassLoader1();
WhichClassLoader2 wcl2 = new WhichClassLoader2();
// Who loaded what?
System.out.println("Object was loaded by " + obj.getClass().getClassLoader());
System.out.println("WCL1 was loaded by " + wcl1.getClass().getClassLoader());
System.out.println("WCL2 was loaded by " + wcl2.getClass().getClassLoader());
wcl2.getTheClass();
}
}
===================================================================
public class WhichClassLoader2 {
// This method is invoked from WhichClassLoader1
public void getTheClass() {
WhichClassLoader3 wcl3 = new WhichClassLoader3();
System.out.println("WCL3 was loaded by " + wcl3.getClass().getClassLoader());
}
}
如果所有的WhichClassLoaderX类已经放到应用程序类路径(application class path),那么这三个类就由应用程序类装入器加载,例子会正常运行。下载假设你将WhichClassLoader2.class打包到一个JAR文件中,并且将它放到Java_home/jre/lib/ext路径,你在例2中会看到如下输出。


例2:NoClassDefFoundError exception trace

Bootstrap classpath
=C:\WebSphere\AppServer\java\jre\lib\vm.jar;C:\WebSphere\AppServer\java\jre\lib\core.jar;C:\WebSphere\AppServer\java\jre\lib\charsets.jar;C:\WebSphere\AppServer\java\jre\lib\graphics.jar;C:\WebSphere\AppServer\java\jre\lib\security.jar;C:\WebSphere\AppServer\java\jre\lib\ibmpkcs.jar;C:\WebSphere\AppServer\java\jre\lib\ibmorb.jar;C:\WebSphere\AppServer\java\jre\lib\ibmcfw.jar;C:\WebSphere\AppServer\java\jre\lib\ibmorbapi.jar;C:\WebSphere\AppServer\java\jre\lib\ibmjcefw.jar;C:\WebSphere\AppServer\java\jre\lib\ibmjgssprovider.jar;C:\WebSphere\AppServer\java\jre\lib\ibmjsseprovider2.jar;C:\WebSphere\AppServer\java\jre\lib\ibmjaaslm.jar;C:\WebSphere\AppServer\java\jre\lib\ibmjaasactivelm.jar;C:\WebSphere\AppServer\java\jre\lib\ibmcertpathprovider.jar;C:\WebSphere\AppServer\ja
va\jre\lib\server.jar;C:\WebSphere\AppServer\java\jre\lib\xml.jar
Extensions classpath =C:\WebSphere\AppServer\java\jre\lib\ext
Application classpath=.
Exception in thread "main" java.lang.NoClassDefFoundError:
WhichClassLoader3
at java.lang.J9VMInternals.verifyImpl(Native Method)
at java.lang.J9VMInternals.verify(J9VMInternals.java:59)
at java.lang.J9VMInternals.initialize(J9VMInternals.java:120)
at WhichClassLoader1.main(WhichClassLoader1.java:17)

可以看到,程序出现了NoClassDefFoundError异常,听起来有些奇怪,因为WhichClassLoader3就在应用程序类路径上。问题是现在它在错误的类路径上。

问题出在WhichClassLoader2是由扩展类装入器加载的。事实上,应用程序类装入器委托扩展类装入器加载类WhichClassLoader2,进而委托引导程序类装入器。因为引导程序类装入器无法找到这个类,所以类加载控制权返回给扩展类装入器。扩展类装入器在它的类路径找到这个类并加载了它。

当一个类被一个类装入器加载后,该类需要的任何新类都用同一个类装入器加载(按照父级委托模型,追溯到结构上层)。所以当WhichClassLoader2要使用WhichClassLoader3时,首先请求扩展类装入器加载它。扩展类装入器先将请求委托给引导程序类装入器,引导程序类装入器找不到这个类,然后尝试自己加载,但是没有找到该类,因为WhichClassLoader3没在扩展类路径上,而是在应用程序类路径上。并且扩展类装入器不能把请求委托给应用程序类装入器(委托请求只能沿着模型结构向上,不能向下),所以NoClassDefFoundError异常就产生了。


注意:开发者经常通过类加载机制使用如下语法加载配置文件
Properties p = new Properties();
p.load(MyClass.class.getClassLoader().getResourceAsStream("myApp.properties"));

这意味着,如果类MyClass由扩展类装入器加载,并且myApp.properties只在应用程序类路径上,那么加载该配置文件会失败。
WebSphere类装入器概述
注意:在讨论下面的问题时,记住每个JVM都有自己的类装入器。在WebSphere多应用服务器(JVMs)环境下,这意味着JVMs的类装入器是完全独立的,即使他们运行在相同的物理机器上。

WebSphere提供多种自定义可委托的类装入器,见图2:

图2 WebSphere类装入器结构图

最上面的方框代表Java(引导程序,扩展程和应用程序)类装入器。WebSphere在这里加载足够的类来使自身启动并初始化WebSphere扩展类装入器。


WebSphere扩展类装入器
WebSphere自身在WebSphere扩展类装入器加载。WebSphere V6.1版本之前,运行时由这个类装入器单独加载。然而,从V6.1开始,WebSphere被打包成一组OSGi包。每一个OSGi包由独立的类装入器加载。这个OSGi类装入器网络通过OSGi网关类装入器与扩展类装入器和类装入器层次结构中的其他类装入器相连。

不管WebSphere加载自己类的方式如何改变,和你的应用程序相关的行为不会改变。他们仍然保持相同的可见性,相同的类加载选项。

在V6.1之前,WebSphere运行时类文件存在install_root目录下的classes,lib,lib\ext和installedChannels目录中。由于使用OSGi打包方式,这些目录不再存在并且运行时类存在install_root\plugins目录中。

扩展类装入器使用的类路径是从ws.ext.dirs系统属性中提取的,它最开始来源于setupCmdLine脚本中的WAS_EXT_DIRS环境变量。ws.ext.dirs默认值建例3.

例3 ws.ext.dirs默认值
SET
WAS_EXT_DIRS=%JAVA_HOME%\lib;%WAS_HOME%\classes;%WAS_HOME%\lib;%WAS_HOM
E%\installedChannels;%WAS_HOME%\lib\ext;%WAS_HOME%\web\help;%ITP_LOC%\p
lugins\com.ibm.etools.ejbdeploy\runtime

ws.ext.dirs环境变量中的每一个目录被加到WebSphere扩展类装入器类路径中,并且该路径中的每一个JAR文件和每个ZIP文件被加到类路径中。

即使classes和installedChannels路径在install_root路径下不存在,setupCmdLine脚本也会把它们加到扩展类路径中。这意味着,如果你在原来的版本中在这些目录里加入了自己的JAR文件,扩展类装入器也会加载它们。然而,我们不建议这种方式,你应该从老版本迁移到新版本。

另一方面,V6.1之前的版本,如果你开发Java应用依赖于install_root\lib目录下的JAR文件,你需要修改你的程序来保持兼容性。WebSphere Application Server专为这种应用程序提供了两个瘦客户端库(thin client libraries):一个是管理客户端库(administrative client library),一个是Web services客户端库。这些瘦客户端库可以在install_root\runtimes目录中找到:

? com.ibm.ws.admin.client_7.0.0.jar
? com.ibm.ws.webservices.thinclient_7.0.0.jar

这些库提供了你的应用程序与WebSphere一起工作所需的所有东西。WebSphere Application Server V7为用户提供了限制访问WebSphere内部类的能力,所以你的应用程序不应该调用官方未发布的不被支持的API。在应用程序服务器设置中,这一项叫做“内部服务类访问(Access to internal server classed)”。

默认设置是“允许(Allow)”,这意味着你的应用程序可以不受限制地访问未公开的内部WebSphere类。我们不建议使用该功能,并且在后续版本中将会禁止。因此,作为一个管理员,将该设置改为“限制(Restrict)”,然后程序是否运行正常是一个不错的想法。如果它们依赖与未公开的WebSphere内部类,你会收到ClassNotFoundException,在这种情况下,你可以再改回“允许”。开发人员应该修改它们的应用,不再调用未公开的WebSphere内部类,从而兼容WebSphere Application Server的后续版本。

应用程序和Web模块类装入器
Java EE5应用程序五个主要元素:Web模块,EJB模块,应用程序客户端模块,资源适配器(RAR文件),工具JAR包。EJB和servlets都会用到工具JAR包中的代码。类似于log4j的工具框架就是工具JAR的好例子。

与一个应用程序相关的EJB模块,工具JAR包,资源适配器文件和共享库文件通常在一个类装入器中。这个类装入器叫做应用程序类装入器。依赖于类装入器策略,该类装入器可以在多个应用程序(EARs)间共享,或者默认被一个应用程序使用。

默认地,Web模块有自己的类装入器,一个WAR类装入器,来加载WEB-INF/classes和WEB-INF/lib目录下的内容。你可以通过修改应用程序的WAR类装入器策略来改变默认的行为。策略设置可以在控制台中找到。应用程序->WebSphere企业应用程序->应用程序名称->类装入和更新检测->WAR类装入器策略。见图3.


图3 WAR类装入器策略

默认的是设置为“应用程序中每个WAR文件的类装入器”。这个设置在以前的版本中称为“模块”,在应用程序部署描述符中可以通过Rational Application Developer中看到。

如果WAR类装入器策略设置为“应用程序的单个类装入器”,Web模块的内容和EJB,RAR,工具JAR,共享库一样由应用程序类装入器加载。应用程序类装入器是WAR类装入器的父级类装入器。这个设置在以前的版本中称为“应用程序”,在应用程序部署描述符中可以通过Rational Application Developer中看到。

应用程序和WAR类装入器是可以重复加载的类装入器。它们监控应用程序代码的变化来自动重新加载变化的类。你可以在开发时改变这种行为。

处理JNI代码

因为JVM只有一个地址空间,本地代码在一个地址空间智能被加载一次,JVM规范指出在JVM中本地代码只能被一个类装入器加载。

这会导致一个问题,如果你有一个应用程序(EAR文件)包括两个Web模块,并且它们都需要通过JNI加载相同的本地代码。那么,只有第一个被加载的库会成功。

为了解决这个问题,你可以把加载本地代码的几行Java代码提出来,放到一个类中,然后让这个类由应用程序类装入器加载(放到一个工具JAR中)。然而,如果你在同一个应用服务器中部署了多个这样的应用程序(EAR文件),你不得不把这个类放到WebSphere的扩展类装入器上,以保证在每个JVM中本地代码只被加载一次。

如果本地代码被放在可多次加载的类装入器上(例如:如应用程序类装入器和WAR类装入器),确保本地代码可以正确的卸载自身并且Java代码可以正确重新加载。WebSphere对本地代码无法控制,如果它无法正确卸载或加载,应用程序会失败。

如果一个本地库依赖与另一个,问题变得更加复杂。想了解详情,请在信息中心查询“独立本地库”。

配置WebSphere的类装入器
在前面,我们学习了WebSphere类装入器和它们如何共同工作来加载类的。在WebSphere Application Server中有一些设置可以影响类加载行为。这一节,我们来讨论这些选项。

应用服务器类装入器策略
对于系统中的每一个应用服务器,类加载策略可以被设置成“单个(Single)”或“多个(Multiple)”。这些设置可以在管理控制台找到:服务器->服务器类型->WebSphere Application Server->服务器名称。见图4.


图4 应用服务器类装入器设置

当应用程序服务器类加载策略设置为单个(Single)时,在应用程序服务器(JVM)中加载EJB,工具JAR和共享库时,会使用单独的应用程序类装入器。如果WAR类装入器策略被设置为“应用程序的单个类装入器”,这个特殊应用程序的Web模块内容也被这个单个类装入器加载。

当应用服务器类加载策略默认情况下设置为“多个”时,每个应用程序加载EJB,工具JAR包和共享库时会得到它们自己的类装入器。如果WAR类装入器策略设置为“应用程序中每个WAR文件的类装入器”,Web模块将会收到自己的类装入器;如果设置为“应用程序的单个类装入器”,Web模块不会收到自己的类装入器。

这里有个例子可以演示。假设你有两个应用程序,Application1和Application2,运行在同一个应用服务器。每个应用程序有一个EJB模块,一个工具JAR,和两个Web模块。如果应用程序服务器类装入器策略设置为“多个”,并且每个Web模块的类装入器策略设置为“应用程序中每个WAR文件的类装入器”,结果如图5所示。


图5 类装入器策略:例1

每个应用程序与其他应用程序相隔离,用一个应用程序中的Web模块也与其他Web模块相隔离。WebSphere默认的类装入器策略导致应用程序之间和模块之间的完全隔离。

如果你将WAR2-2模块类装入器策略改变为“应用程序的单个类装入器”,解雇如图6所示。


图6 类装入器策略:例2

Web模块WAR2-2被Application2的类装入器所加载,例如:Util2.jar中的类可以看到WAR2-2的/WEB-INF/classes和/WEB-INF/lib目录下的类。

在最后一个例子中,如果我们将应用服务器类装入器策略改为“单个”,并且将WAR2-1的类装入器策略改为“应用程序的单个类装入器”,结果如图7所示。


图7 类装入器策略:例3

现在,对于Application1和Application2只有一个应用程序类装入器。Util1.jar中的类可以看到EJB2.jar,Util2.jar,WAR2-1.war和WAR2-2.war中的类。应用程序类装入器加载的类仍然无法看到WAR1-1和WAR1-2模块中的类,因为类装入器只能找到层次结构上层的类,而不能找到下层的。

类加载/委托模式
WebSphere应用程序类装入器和WAR类装入器都有一个类装入器顺序的设置(参见图4)。该设置决定了类装入器顺序是否要遵照普通的Java类装入器的委托机制还是要覆盖它(在前面“Java类装入器介绍”中描述)。

对类加载模式,这里有两个选项:
? 类已装入并且是先使用父类装入器
? 类已装入并且是先使用本地类装入器(父类最后)

在以前的WebSphere版本中,这些设置分别称作父类优先(PARENT_FIRST)和父类最后(PARENT_LAST)。

默认的类加载模式是“类已装入并且是先使用父类装入器”。这种模式导致类装入器在尝试本地类路径上加载类之前先委托父级类加载加载。这也是默认的Java类装入器策略。

如果类加载策略设置为“类已装入并且是先使用本地类装入器(父类最后)”,类装入器会现在本地类路径上加载类,然后再委托父级类装入器。这种策略可以让应用程序类装入器为父级类装入器已经存在的类提供自己的版本。

注意:管理控制台在这一点比较让人迷惑。Web模块的配置中也有“类已装入并且是先使用父类装入器”和“类已装入并且是先使用本地类装入器(父类最后)”。然而,这里的“本地类装入器”实际上是指WAR类装入器,所以“类已装入并且是先使用本地类装入器”应该是“类已装入并且是先使用WAR类装入器”。

假设你有一个应用程序,类似于前面例子中的Application1,并且它使用流行的log4j在EJB模块和两个Web模块中记录日志。同时,假设每个模块有自己的log4j.properties。你可以将log4j配置成工具JAR,这样你就可以在EAR文件只有一个文件了。

然而,如果你那么做,你会惊奇地发现,所有模块,包括Web模块都从EJB模块加载log4j.properties。原因是,当Web模块初始化log4j包时,log4j类由应用程序类装入器加载。Log4j被配置成工具JAR。Log4j然后在它的类路径上查找log4j.properties,结果在EJB模块找到了。

即使你么有在EJB模块中使用log4j,EJB模块也没有log4j.properties文件,log4j也不会再Web模块中找到log4j.properties。因为类装入器只能沿着层次结构向上查找类,而不能向下。

为了结果这个问题,你可以使用下面的方法:
? 创建一个单独的文件,例如:Resource.jar,将它配置为工具JAR,将所有的log4j.properties从模块移动到这个文件中,并且使它们名称唯一(比如war1-1_log4j.properties,war1-2_log4j.properties和ejb1_log4j.properties)。当从每个模块初始化log4j的时候,告诉它加载正确的配置文件,而不是默认的log4j.properties。
? 保持Web模块中的log4j.properties不变(/WEB-inf/classes),为每个Web模块(/WEB-INF/lib)添加log4j.jar并且设置Web模块的类加载模式为“类已装入并且是先使用本地类装入器(父类最后)”。当从Web模块初始化log4j的时候,它从Web模块自身加载log4j.jar,然后在本地类路径找到log4j.properties。当EJB模块初始化log4j时,它从应用程序类装入器加载,并且在相同的类路径找到log4j.properties,在EJB1.jar文件中。
? 如果可能,合并所有的log4j.properties为一个文件,例如Resource.jar,并且把它放到应用程序类装入器。

单例:单例模式用来保证类只初始化一次。然而,一次意味着每个类装入器一次。如果你有一个单例类在两个独立的Web模块中初始化,会创建两个独立的类的实例,每个WAR类装入器加载一个。所以,在多个类装入器环境下,实现单例时要特别小心。

共享库
共享库是一些被多个应用程序使用的文件。Apache Struts和log4j是共享库的常见例子。我们将共享库指向一组JAR包,然后把它们和应用程序,Web模块或者应用服务器类装入器相关联。当你有一个框架的多个版本要关联到不同的应用程序时,共享库尤其有用。

共享库在管理工具中定义。它们包括一个名称,一个Java类路径和加载JNI库的本地路径。我们可以在单元,节点,服务器或集群级别定义它们。然而,只是简单地定义一个库,它是不会被加载的。你必须把它和应用程序,Web模块或者加载共享库中类的应用服务器类装入器相关联。将共享库和应用服务器类装入器相关联,使得服务器上的所有应用程序都可以使用这个库。

注意:如果你将共享库和应用程序相关联,不要将相同的共享库和应用服务器类装入器相关联。

你可以使用如下两种方法将共享库和应用程序相关联。

? 你可以用管理控制台。在企业应用程序中的引用,可以通过共享库引用添加库。
? 你可以使用应用程序和共享库的manifest文件。共享库中包括一个manifest,标记它为扩展功能。应用程序中的manifest文件通过列出库的扩展名来声明了对共享库的依赖。

关于该方法的更多信息,可以在信息中心查找安装选项。

通过管理工具,可以将共享文件和应用服务器类装入器相关联。设置可以在服务器项找到。展开“Java和进程管理”。选择“类装入器”,点击“新建”按钮来定义一个类装入器。定义新的类装入器后,你可以修改它,并且使用“共享库引用”链接把它与需要的共享库关联。

参见“第四步:使用共享库共享工具JAR”获得更多信息。

类装入器查看器
如果类装入器服务没有开启,那么它只显示类装入器的层次结构和类路径,不会显示每个类装入器加载的类。这意味着类装入器查看器的查询功能是无法使用的。

可以通过如下方式开启类装入器查看器服务,服务器->服务器类型->WebSphere Application Server->服务器名称->类装入器查看器服务(其他属性下面)->选中在服务器启动时启动服务。重启应用服务器后,设置生效。

下一节,我们会针对不同类装入器设置给出一些例子,然后我们通过类装入器查看器演示不同的结果。


通过实例学习类装入器
我们已经介绍了影响类装入器行为的不同选项。在这一节,我们举个例子来使用我们前面讨论的不同选项,这样你可以更好地评估你的应用程序的最佳方案。

我们创建了一个非常简单的应用,一个servlet和一个EJB。它们都调用一个类,VersionChecker,如例4所示。这个类可以打印出类是由哪个类装入器加载的。VersionChecker有一个内部值,可以打印我们使用的类的版本。这个会用来演示同一个工具JAR的多个版本的使用。

例4 VersionChecker源代码

package com.itso.classloaders;
public class VersionChecker {
static final public String classVersion = "v1.0";
public String getInfo() {
return ("VersionChecker is " + classVersion + ". Loaded by " + this.getClass().getClassLoader());
}
}

安装后,可以通过http://localhost:9080/ClassloaderExampleWeb/ExampleServlet来访问。访问后会调用ExampleServlet,然后调用VersionChecker并显示类装入器。

VersionCheckerV1.jar包括VersionChecker类文件,返回版本号为1.0。下面的练习,如果没有特别说明,我们使用类装入器策略和加载模式的默认设置。换句话说,应用程序有一个类装入器,WAR文件有一个类装入器。它们的委托模式都设置为“类已装入并且是先使用父类装入器”。

第一步:简单的Web模块
我们假设工具类只有servlet使用。我们将VersionCheckerV1.jar放在Web模块的WEB-INF/lib目录下。

提示:你来放置一个Web模块使用的JAR或者这个Web模块在WEB-INF/lib能看到的JAR。

当我们在这种配置下运行程序的时候,我们得到结果如例5所示。

例5 类装入器:例1
VersionChecker called from Servlet
VersionChecker is v1.0.
Loaded by
com.ibm.ws.classloader.CompoundClassLoader@18721872[war:ClassloaderExam
ple/ClassloaderExampleWeb.war]

Local ClassPath:
C:\WebSphereV7\AppServer\profiles\node40a\installedApps\Cell40\Classloa
derExample.ear\ClassloaderExampleWeb.war\WEB-INF\classes;
C:\WebSphereV7\AppServer\profiles\node40a\installedApps\Cell40\Classloa
derExample.ear\ClassloaderExampleWeb.war\WEB-INF\lib\VersionCheckerV1.jar;
C:\WebSphereV7\AppServer\profiles\node40a\installedApps\Cell40\Classloa
derExample.ear\ClassloaderExampleWeb.war

Parent:
com.ibm.ws.classloader.CompoundClassLoader@7f277f27[app:ClassloaderExample]
Delegation Mode: PARENT_FIRST

通过输出我们可以学到如下一些东西:
1. WAR类装入器的类型:
com.ibm.ws.classloader.CompoundClassLoader
2. 它按照如下顺序搜索类
ClassloaderExampleWeb.war\WEB-INF\classes
ClassloaderExampleWeb.war\WEB-INF\lib\VersionCheckerV1.jar
ClassloaderExampleWeb.war

WEB-INF/classes目录包括一些未打包的资源(例如servlet类,普通Java类和配置文件),当WEB-INF/lib包括一些打包的JAR。你可以选择将Java打包成JAR文件并把它们放到lib目录下,或者你可以不打包,直接把它们放到classes目录下。它们在同一个类路径上。由于我们的实例是在Rational Application Developer上开发的,导出程序时没有将Java类打包成JAR,所以servlet在calsses目录下。


WAR文件的根目录是方式代码和配置文件的第二个地方,但是你不该那么做,因为那个目录是Web服务器(如果文件服务Servlet功能是开启的,这也是默认情况)的文档根目录,也就是说在浏览器中可以访问该目录的任意文件。根据Java EE5规范,WEB-INF是受保护的,所以classes和lib目录放在WEB-INIF下面。

类装入器类路径在应用启动时动态创建。

现在我们可以用类装入器查看器来显示类装入器。在管理控制台,选择故障诊断->类装入器查看器。然后展开server1->应用程序->ClassloaderExample->Web模块,然后点击ClassloaderExampleWeb.war,如图8所示。


图8 类装入器查看器显示应用程序树

当Web模块展开后,类装入器查看器显示出类装入器层次结构,JDK扩展和JDK应用程序类装入器在上面,中间是WAR类装入器,下面是组合类装入器,见图9。


图9 类装入器查看器显示类装入器层次结构

如果你展开com.ibm.ws.classloader.CompoundClassLoader的类路径,你会看到和VersionChecker类打印结果相同的信息(参见例5)。

注意:为了让类装入器查看器显示加载的类,需要开启类装入器查看器服务,可以参考“类装入器查看器”部分。

类装入器查看器有一种表格视图可以在单独一页显示每个类加载器加载的类。表格视图同样可以显示委托模式。True表示类由父级类加载器先加载,false表示类由本地类加载器先加载(父类最后),或者Web模块下的WAR类装入器。见图10。


图10 类装入器查看器表格视图

你可以看到,正如我们所期望的,WAR类装入器已经加载了我们的例子servlet和VersionChecker类。

类装入器查看器还有一个查找功能,在这里你可以查找类,JAR文件,目录等等。在你不知道你感兴趣的类是由哪个类装入器加载时,这个功能非常有用。这个查询功能大小写敏感,但是可以使用通配符,所以你在查找*VersionChecker*时,可以查到我们的VersionChecker类。

第二步:添加EJB模块和工具JAR
下一步,我们要在应用程序中添加EJB,它同样依赖于我们的VersionChecker JAR文件。在这个任务中,我们在EAR的根目录添加一个VersionCheckerV2.jar文件。在这个JAR文件中的VersionChecker类返回版本2.0。为了让它成为扩展类装入器上的工具JAR,我们在EJB模块的manifest文件中添加对它的引用,见例6。

例6 更新EJB模块的MANIFEST.MF
Manifest-Version: 1.0
Class-Path: VersionCheckerV2.jar

现在我们在WEB-INF/classes目录下有一个包含servlet的Web模块,在WEB-INF/lib目录下有一个VersionCheckerV1.jar文件。同时,我们有一个EJB模块,引用了在EAR的根目录的VersionCheckerV2.jar。你认为Web模块会加载哪个版本的VersionChecker类?WEB-INF/lib目录下的版本1.0还是工具JAR中的版本2.0?

测试结果见例7。

例7 类装入器:例2
VersionChecker called from Servlet
VersionChecker is v2.0.
Loaded by
com.ibm.ws.classloader.CompoundClassLoader@404a404a[app:ClassloaderExampleV2]

Local ClassPath:
C:\WebSphereV7\AppServer\profiles\node40a\installedApps\Cell40\Classloa
derExampleV2.ear\ClassloaderExampleEJB.jar;C:\WebSphereV7\AppServer\pro
files\node40a\installedApps\Cell40\ClassloaderExampleV2.ear\VersionCheckerV2.jar

Parent: com.ibm.ws.classloader.ProtectionClassLoader@a540a54

Delegation Mode: PARENT_FIRST

VersionChecker called from EJB
VersionChecker is v2.0.
Loaded by
com.ibm.ws.classloader.CompoundClassLoader@404a404a[app:ClassloaderExampleV2]

Local ClassPath:
C:\WebSphereV7\AppServer\profiles\node40a\installedApps\Cell40\Classloa
derExampleV2.ear\ClassloaderExampleEJB.jar;C:\WebSphereV7\AppServer\pro
files\node40a\installedApps\Cell40\ClassloaderExampleV2.ear\VersionCheckerV2.jar
Parent: com.ibm.ws.classloader.ProtectionClassLoader@a540a54
Delegation Mode: PARENT_FIRST

你可以看到,从EJB模块和Web模块调用的VersionChecker都是版本2.0。原因是WAR类装入将请求委托给父级类装入器,而不是自己加载。因此,不管是在servlet还是在EJB中调用的,工具JAR都是有同一个类装入器加载的。

第三步:改变WAR类装入器的委托模式
如果我们想让Web模块使用WEB-INF/lib目录下的VersionCheckerV1.jar呢?对于这个任务,我们需要将类装入器的委托模式从父类优先改成父类最后。

通过以下步骤将委托模式改为父类最后(PARENT_LAST):
1. 在导航栏选择WebSphere企业应用程序
2. 选择ClassloaderExample应用
3. 选择管理模块
4. 选择ClassloaderExampleWeb模块
5. 将类装入器顺序改为“类已装入并且是先使用本地类装入器(父类最后)”。记住,我们在“类加载/委托模式”一节讲过,这里其实应该叫做“类已装入并且是先使用WAR类装入器”。
6. 点击确定
7. 保存配置
8. 重新启动应用程序

WEB-INF/lib中的VersionCheckerV1返回类的版本为1.0。你可以在例8中看到WAR文件中使用的是哪个版本。

例8 类装入器:例3
VersionChecker called from Servlet
VersionChecker is v1.0.
Loaded by
com.ibm.ws.classloader.CompoundClassLoader@1c421c42[war:ClassloaderExam
pleV2/ClassloaderExampleWeb.war]

Local ClassPath:
C:\WebSphereV7\AppServer\profiles\node40a\installedApps\Cell40\Classloa
derExampleV2.ear\ClassloaderExampleWeb.war\WEB-INF\classes;C:\WebSphere
V7\AppServer\profiles\node40a\installedApps\Cell40\ClassloaderExampleV2
.ear\ClassloaderExampleWeb.war\WEB-INF\lib\VersionCheckerV1.jar;C:\WebS
phereV7\AppServer\profiles\node40a\installedApps\Cell40\ClassloaderExam
pleV2.ear\ClassloaderExampleWeb.war
Parent:
com.ibm.ws.classloader.CompoundClassLoader@1a5e1a5e[app:ClassloaderExampleV2]

Delegation Mode: PARENT_LAST

VersionChecker called from EJB
VersionChecker is v2.0.
Loaded by
com.ibm.ws.classloader.CompoundClassLoader@1a5e1a5e[app:ClassloaderExampleV2]

Local ClassPath:
C:\WebSphereV7\AppServer\profiles\node40a\installedApps\Cell40\Classloa
derExampleV2.ear\ClassloaderExampleEJB.jar;C:\WebSphereV7\AppServer\pro
files\node40a\installedApps\Cell40\ClassloaderExampleV2.ear\VersionCheckerV2.jar
Parent: com.ibm.ws.classloader.ProtectionClassLoader@a540a54

Delegation Mode: PARENT_FIRST


提示: 使用这种技术可以使得Web模块使用库的特定版本,例如Struts,或者覆盖WebSphere运行时的类。将普通版本放在层次结构的顶部,将特定版本放在WEB-INF/lib目录下。

Java EE 5规范没有提供标准的选项来指定EAR文件中的委托模式。然而,使用WebSphere扩展EAR文件,你可以指定该设置就不用每次重新部署应用后来改变它了。

如果你使用类装入器查看器的查找功能来查找“*VersionChecker*”,你会看到两个结果,见例9。

例9: 类装入器查看器的查询功能
WAS Module Compound Class Loader (WAR class loader):
file: / C: / WebSphereV7 / AppServer / profiles / node40a /
installedApps / Cell40 / ClassloaderExampleV2.ear /
ClassloaderExampleWeb.war / WEB-INF / lib / VersionCheckerV1.jar

WAS Module Jar Class Loader (Application class loader):
file: / C: / WebSphereV7 / AppServer / profiles / node40a /
installedApps / Cell40 / ClassloaderExampleV2.ear /
VersionCheckerV2.jar

第四步:使用共享库共享工具JAR
在这种情况下,只有一个应用程序使用VersionCheckerV2.jar文件。如果你想在多个应用程序中共享它,该怎么做呢?当然,你可以在每个EAR文件中把它打包进去。但是,如果工具JAR改变了,需要重新部署所有应用。为了避免这个问题,你可以通过共享库发布全局工具JAR。

共享库可以在单元,节点,应用服务器和集群级别定义。定义共享库后,你必须将它与应用服务器,应用程序或单独的Web模块的类装入器相关联。这依赖于共享库作用的目标,WebSphere会使用适当的类装入器来加载共享库。

你可以定义任意多的共享库。你可以把多个共享库和应用程序,Web模块或者应用服务器相关联。

在应用程序级别使用共享库
定义一个共享库VersionCheckerV2_SharedLib,将它与我们的程序ClassloaderTest相关联,需要如下步骤:
1. 在管理控制台,选择环境->共享库
2. 选择共享库的作用域,例如单元,点击新建
3. 指定图11所示的属性

图11 共享库配置
-名称:输入VersionCheckerV2_SharedLib
-类路径:输入类路径上的项,每项间按回车。注意,如果你需要绝对路径,我们建议你使用WebSphere环境变量,例如%FRAMEWORK_JARS%/VersionCheckerV2.jar。确保该变量作用域和共享库相同。
-本地库路径:输入JNI使用的DLL和.so文件
-(V7新增)如果你想在应用程序间共享的类只有一个实例,选择“为该共享库使用隔离的类装入器”。
4. 点击确定
5. 选择应用程序->应用程序类型->WebSphere企业应用程序
6. 选择ClassloaderExample应用
7. 在引用中,选择共享库引用
8. 选择ClassloaderExample
9. 选择引用共享库
10. 选择VersionCheckerV2_SharedLib,然后点击>>按钮来移动到已选列,如果12所示。

图12 选择一个共享库
11. 点击确定
12. ClassloaderExample共享库配置窗口如图13所示

图13 ClassloaderExample程序设置共享库
13. 点击确定,保存配置

如果我们从EAR文件的根目录将VersionCheckerV2.jar移走,并从EJB模块的manifest文件中移除对它的引用,然后重新启动应用服务器,我们会看到如例10所示的结果。记住Web模块的类装入器顺序还是“类已装入并且是先使用本地类装入器(父类最后)”。

例10 类装入器:例5
VersionChecker called from Servlet
VersionChecker is v1.0.
Loaded by
com.ibm.ws.classloader.CompoundClassLoader@405d405d[war:ClassloaderExam
pleV3/ClassloaderExampleWeb.war]

Local ClassPath:
C:\WebSphereV7\AppServer\profiles\node40a\installedApps\Cell40\Classloa
derExampleV3.ear\ClassloaderExampleWeb.war\WEB-INF\classes;C:\WebSphere
V7\AppServer\profiles\node40a\installedApps\Cell40\ClassloaderExampleV3
.ear\ClassloaderExampleWeb.war\WEB-INF\lib\VersionCheckerV1.jar;C:\WebS
phereV7\AppServer\profiles\node40a\installedApps\Cell40\ClassloaderExam
pleV3.ear\ClassloaderExampleWeb.war Parent:
com.ibm.ws.classloader.CompoundClassLoader@3e793e79[app:ClassloaderExampleV3]
Delegation Mode: PARENT_LAST

VersionChecker called from EJB
VersionChecker is v2.0.
Loaded by
com.ibm.ws.classloader.CompoundClassLoader@3e793e79[app:ClassloaderExampleV3]

Local ClassPath:
C:\WebSphereV7\AppServer\profiles\node40a\installedApps\Cell40\Classloa
derExampleV3.ear\ClassloaderExampleEJB.jar;C:\Documents and
Settings\Administrator\My Documents\0-Working files\Admin and
Config\Downloads\VersionCheckerV2.jar Parent:
com.ibm.ws.classloader.ProtectionClassLoader@7d677d67
Delegation Mode: PARENT_FIRST

正如我们所期望的,由于Web模块的委托模式的存在,当servlet需要VersionChecker类时,加载了VersionCheckerV1.jar。当EJB需要VersionChecker类时,它是从共享库加载的,指向C:\henrik\VersionCheckerV2.jar。

如果我们想让Web模块也使用共享库,我们只需要把Web模块类装入器的顺序改回默认值,“类已装入并且是先使用父类装入器”。

在应用服务器级别使用共享库
共享库也可以和应用服务器相关联。所有部署在该服务器上的应用都可以看到共享库的代码。要想将共享库和应用服务器关联,你必须为应用服务器先创建一个额外的类装入器,方法如下:
1. 选择应用服务器
2. 在服务器栏,展开Java和进程管理,选择类装入器
3. 选择新建,然后为类装入器选择类装入器顺序,“类已装入并且是先使用父类装入器”或者“类已装入并且是先使用本地类装入器(父类最后)”。点击确定。
4. 点击刚创建的类装入器
5. 点击共享库引用
6. 点击添加,选择你想和应用服务器关联的库。重复上面操作来关联多个库。在我们的例子中,我们选择VersionCheckerV2_SharedLib。
7. 点击确定。
8. 保存配置。
9. 重新启动应用服务器,让配置生效。

由于我们已经将VersionCheckerV2共享库和应用服务器的类装入器相关联,我们会得到如例11所示的结果。

例11 类装入器:例6
VersionChecker called from Servlet
VersionChecker is v1.0.

Loaded by
com.ibm.ws.classloader.CompoundClassLoader@26a426a4[war:ClassloaderExam
pleV3/ClassloaderExampleWeb.war]

Local ClassPath:
C:\WebSphereV7\AppServer\profiles\node40a\installedApps\Cell40\Classloa
derExampleV3.ear\ClassloaderExampleWeb.war\WEB-INF\classes;C:\WebSphere
V7\AppServer\profiles\node40a\installedApps\Cell40\ClassloaderExampleV3
.ear\ClassloaderExampleWeb.war\WEB-INF\lib\VersionCheckerV1.jar;C:\WebS
phereV7\AppServer\profiles\node40a\installedApps\Cell40\ClassloaderExam
pleV3.ear\ClassloaderExampleWeb.war Parent:
com.ibm.ws.classloader.CompoundClassLoader@1f381f38[app:ClassloaderExampleV3]

Delegation Mode: PARENT_LAST

VersionChecker called from EJB
VersionChecker is v2.0.

Loaded by com.ibm.ws.classloader.ExtJarClassLoader@48964896[server:0]

Local ClassPath: C:\Documents and Settings\Administrator\My
Documents\0-Working files\Admin and
Config\Downloads\VersionCheckerV2.jar
Parent: com.ibm.ws.classloader.ProtectionClassLoader@7d677d67
Delegation Mode: PARENT_FIRST

我们新建的类装入器叫做ExtJarClassLoader,当EJB模块请求时,它加载了VersionCheckerV2.jar。根据委托模式,WAR类装入器也会加载自己的版本。


提示: 进一步的详细信息,可以参考IBM JDK6 诊断指南,在DeveloperWorks的如下地址可以找到:
http://www.ibm.com/developerworks/java/jdk/diagnosis/60.html

参考资料
原文地址:http://www.redbooks.ibm.com/redpapers/pdfs/redp4581.pdf
说明
图片比较直观,因此文中图片采用英文原版图片,没有改成中文版。由于水平有限,翻译中出现错误在所难免,如果对本文有疑问请参考原文。欢迎提出宝贵意见,可以通过邮箱和我联系:upeepu@gmail.com。

?

  相关解决方案