12.3.1类加载策略
对于系统中的每个应用程序服务器,类加载策略可以设置成Single或者Multiple。当应用程序类加载策略设置成Single,单一的应用程序类加载器可以加载应用程序服务器(JVM)中的所有EJB、工具JARs和共享库。如果WAR类加载策略设置成Single类加载(或者Application),这个应用程序中的Web模块也会被这个single类加载器加载。
当应用程序服务器类加载策略被设置成Multiple,缺省值,每个应用程序会使用自己的类加载器加载EJB、工具JAR和共享库。依赖于WAR类加载器加载策略是设置成应用程序中每个WAR文件使用自己的类加载器(或者称为Module),还是设置成Single类加载(或者称为Application),Web模块能够或者不能使用自己的类加载器。
下面用一个例子说明。我们有两个应用程序,程序1和程序2,它们运行在同一个应用程序服务器上。每个应用程序有一个EJB模块,一个工具JAR和两个Web模块。如果应用程序服务器自己的类加载策略设 置成缺省值Multiple ,所有Web模块的类加载策略也设置成缺省值,即应用程序中的WAR文件都有自己的类加载器,如下图12-3:
图12-3类加载器策略:例一
每一个应用程序被完全的分开,每个应用程序当中的Web模块也被完全的分开。WebSphere缺省的类加载策略就是应用程序和模块都是完全隔离的。
如果我们现在把WAR2-2模块的类加载策略修改成Single,就会变成下图12-4:
图12-4类加载策略:例二
Web模块WAR2-2由程序2的类加载器加载,Util2.jar中的类能够看到WAR2-2的/WEB-INF/classes和/WEB-INF/lib目录下的类。
在上一个例子中,如果我们把应用服务器的类加载策略从Multiple改成Single,把WAR2-1模块的类加载策略从Module改成Single,结果就变成了下面的内容12-5:
图12-5类加载策略:例三
现在只剩下一个应用程序类加载器加载程序1和程序2的。Util1.jar中的类能够看到EJB2.jar、Util2.jar、WAR2-1.war和WAR2-2.war的类。然而,应用程序类加载器仍然看不到WAR1-1和WAR1-2的类,因为类加载器只能向上查看类而不能向下查看类。
12.3.2类加载/委托模式
WebSphere的应用程序类加载器和WAR类加载器都有一个称为类加载顺序的设置。这个设置决定了是否遵循正常的Java的类加载委托机制(见Java类加载器介绍)还是覆盖它。
类加载模式有两个可能的选项:
父类优先
应用程序优先
在WebSphere的早期版本中,这个设置称为PARENT_FIRST和PARENT_LAST。缺省类加载模式是父类优先(PARENT_FIRST)。这个设置要求类加载器在加载自己类路径中的类之前先加载父类。这是标准Java类加载器的缺省策略。
如果类加载策略设置成应用程序优先(PARENT_LAST),类加载器就会在加载父类之前,先把自己的类路径中的类加载进来。这个策略允许应用程序类加载器覆盖和提供已经在父加载器中存在的自己版本的类。
注意: 在这一点上,管理控制台有点模糊。在Web模块的配置界面,类加载顺序有两个选项:父类加载器优先和应用程序类加载器优先。然而,根据上下文,这里的“应用程序类加载器”其实指的是WAR类加载器,所以应用程序类加载器优先其实指的是WAR类加载器优先。
假如你有一个应用程序,类似于之前例子中的程序1,EJB模块和两个Web模块都使用log4j生成日志。假定每一个模块把自己唯一的log4j.properties文件打包进模块文件中。在EAR文件中,你只需要把log4j配置成工具JAR就可以了。然而,如果这样做,你可能会奇怪所有的模块都会看到这个jar文件,包括Web模块,尽管log4j.properties文件是EJB模块加载的。
原因是,当Web模块初始化log4j包时,应用程序类加载器加载了log4j的类。Log4j被配置成工具JAR。Log4j会在自己类路径下查找log4j.properties这个文件,在EJB模块中发现了它。
如果EJB模块不使用log4j记录日志,EJB模块中也不包含log4j.properties文件, log4j不会在任何的Web模块中找到log4j.properties这个文件。原因是类加载器只能够向上查找类,而不能向下。
要解决这个问题,可以这么做:
1. 单独创建一个文件,比如,Resource.jar,把它配置成工具JAR,把所有的log4j.properties都移到这个文件里面,但是要保证文件名唯一(比如war1-1_log4j.properties、war1-2_log4j.properties和ejb1_log4j.properties)。当从每个模块中初始化log4j的时候,告诉它为模块装入正确的配置文件,而不是都使用缺省(log4j.properties)。在原始位置(/WEB-INF/classes)存放Web模块的log4j.properties,把log4j.jar添加到所有的Web模块(/WEB-INF/lib)下,设置Web模块的类加载模式为应用程序类加载优先(PARENT_LAST)。当从Web模块中初始化log4j,模块自己装入log4j.jar,log4j在自己的本地类路径下找到log4j.properties。当EJB模块初始化log4j的时候,从应用程序类加载中加载,在相同的类路径下找到EJB1.jar中的log4j.properties.
2. 如果可能,把所有的log4j.properties文件合并成一个,放在应用程序类加载器中,比如放在Resource.jar文件中。
12.3.3共享库
共享库是多个应用可以公用的文件。典型的例子是框架的使用,比如Apache Struts或者log4j。可以让共享库指向一个JAR集合,把这个JAR文件跟应用程序、Web模块或者应用程序服务器类加载关联起来。当你有多个不同版本的相同 框架,你希望跟不同的应用程序关联,这个时候就能够使用共享库。共享库通过控制台进行定义。定义项包括一个象征性的名字,Java类路径和装入JNI库的native路径。可以在单元、节点、服务器或者集群中定义共享库。但是,简单的定义一个库不会装入这个库的。必须把这个库跟一个应用程序、Web模块或者应用程序服务器的类加载器关联起来,这个共享库才能够被加载。如果共享库跟应用程序服务器类加载器关联起来,服务器上的所有应用程序都能够使用这个库。
注意: 如果已经把一个共享库跟应用程序关联起来,就不要把同一个共享库跟这个应用程序服务器关联起来。
可以通过如下两种方法关联共享库:
1. 可以使用管理控制台。在企业应用程序界面,引用选项可以关联到共享苦,创建共享库。
2. 可以使用应用程序的manifest文件和共享库。库的依赖会在应用程序manifest文件中指明,在扩展列表中罗列出库的扩展名。
使 用管理控制台,将共享库跟应用程序服务器的类加载器关联起来。这个设置能够在服务器基础架构中找到。展开Java和进程管理,选择Class loader,单击New按钮,定义一个新的类加载。一旦定义了一个新的类加载器,你可以修改它,同时使用共享库引用连接,可以把这个共享库跟新建的类加载器关联起来。
请查看“步骤4::使用共享库共享工具JAR”
12.4类加载查看器
V6.0.2新特性: WebSphere Application Server V6.0.2引入了一个新的工具,类加载查看器。一旦激活,这个工具能够帮助你诊断类加载问题,显示不同类加载器,设置以及每个的类加载情况。
如果类加载查看器服务不可用,类加载查看器只能显示类加载器的层次结构以及他们的路径,而不是每个类加载器的具体类加载情况。也就是类加载查看器的查询功能缺省是不可用。
要启动类加载查看器服务,执行如下:Servers→Application Servers→<server name> ,选择Additional Properties链接下的Class Loader Viewer Service,接着选择Enable service at server startup。完成这个设置,需要重新启动应用服务器才能够生效。
在下一节,我们会给出一个例子,说明不同类加载器如何工作以及使用类加载查看器记录不同的结果。
12.5通过案例学习类加载
我们已经介绍了多个影响类加载的选项。这个部分,会举一个例子来说明这些。
创建一个简单的应用程序,有一个servlet,一个EJB。它们都调用一个类,VersionChecker,见例12-4。这个类可以打印出哪一个类加载器加载这个类。VersionChecker类还有一个内部值, 显示正在使用的是哪个版本的类。这个会在后面用到,用来描述同一个工具JAR的不同版本的使用情况。
例12-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。它启动了调用了VersionChecker的ExampleServlet,会显示出例12-5中的信息:
例12-5调用ExampleServlet
VersionChecker is v1.0.
Loaded bycom.ibm.ws.classloader.CompoundClassLoader@71827182
Local ClassPath:
C:WebSphereAppServerprofilesAppSrv02installedAppskcgg1d8Node02Cel
lClassloaderExample.earClassloaderExampleWeb.warWEB-INFclasses;C:W
ebSphereAppServerprofilesAppSrv02installedAppskcgg1d8Node02CellCl
assloaderExample.earClassloaderExampleWeb.warWEB-INFlibVersionCheck
erV1.jar;C:WebSphereAppServerprofilesAppSrv02installedAppskcgg1d8
Node02CellClassloaderExample.earClassloaderExampleWeb.war
Delegation Mode: PARENT_FIRST
VersionCheckerV1.jar文件包含了VersionChecker类文件,它返回版本数是1.0。如下测试,如果没有特别指出,类加载策略和加载模式都使用缺省值。换句话说,应用程序有一个类加载器,WAR文件有一个。 两个的委托模式都设置成了父加载器优先(PARENT_FIRST)。我们假定应用程序已经加载到了一个名为AppSrv02的应用服务器上了。
12.5.1步骤1:简单的Web模块打包
假定这种情况:我们的工具类只被一个servlet调用。把VersionCheckerV1.jar文件放在WEB-INF/lib目录下面。 对于这样的配置,运行应用程序时,出现例12-6中的内容。
例12-6类加载例1
VersionChecker called from Servlet
VersionChecker is v1.0.
Loaded bycom.ibm.ws.classloader.CompoundClassLoader@71827182
Local ClassPath:
C:WebSphereAppServerprofilesAppSrv02installedAppskcgg1d8Node02Cel
lClassloaderExample.earClassloaderExampleWeb.warWEB-INFclasses;C:W
ebSphereAppServerprofilesAppSrv02installedAppskcgg1d8Node02CellCl
assloaderExample.earClassloaderExampleWeb.warWEB-INFlibVersionCheck
erV1.jar;C:WebSphereAppServerprofilesAppSrv02installedAppskcgg1d8
Node02CellClassloaderExample.earClassloaderExampleWeb.war
Delegation Mode: PARENT_FIRST
从上述跟踪信息中,可以得到:
1.类加载器的类型是:
com.ibm.ws.classloader.CompoundClassLoader.
2.查找类的顺序是:
ClassloaderExampleWeb.warWEB-INFclasses
ClassloaderExampleWeb.warWEB-INFlibVersionCheckerV1.jar
ClassloaderExampleWeb.war
WEB-INF/classes目录包含了没有打包的资源(比如servlet类、Java类和配置文件),但是WEB-INF/lib目录下就包含了打包好的JAR文件。你可以选择将Java代码打包到JAR文件中,将他们放到库目录下面或者可以未打包直接放在类路径下面。他们会在同一 个类路径下面。由于我们的示例应用程序都是使用Application Server Toolkit开发和导出的,servlet直接就在类目录下面,在导出应用程序的时候,toolkit不会把Java类打包成一个JAR文件。WAR文件的根是下一个能够存放代码或者配置文件的地方,但是不建议这么用,因为这个目录对于Web服务器是文档根的目录,所以这个目录下的任何内容都能够通过浏 览器直接访问。根据J2EE的规范,WEB-INF是受保护的,这就是为什么classes和lib目录都在WEB-INF目录下。
在应用程序启动的时候,类加载器的类路径是动态构建的,我们可以使用类加载查看器显示类加载。在管理员控制台上,选择Troubleshooting→Class Loader Viewer,展开server1→Applications→ClassloaderExample→Web modules,单击ClassloaderExampleWeb.war,就会看到如下图12-6:
图12-6类加载器显示应用程序树
展开Web模块时,类加载查看器显示了从JDK扩展类加载器到JDK应用程序类加载器再到WAR类加载器的层次结构,称为混合类加载器,参见12-7:
图12-7类加载查看器显示类加载层次
如果你展开WAS模块的类路径—-混合类加载器,可以看到跟VersionChecker打印出来同样的内容。见12-7。
例12-7类加载查看器显示的WAR类加载器的类路径
file:/C:/WebSphere/AppServer/profiles/AppSrv02/installedApps/kcgg1d8Nod
e02Cell/ClassloaderExample.ear/ClassloaderExampleWeb.war/WEB-INF/classe
s
file:/C:/WebSphere/AppServer/profiles/AppSrv02/installedApps/kcgg1d8Nod
e02Cell/ClassloaderExample.ear/ClassloaderExampleWeb.war/WEB-INF/lib/Ve
rsionCheckerV1.jar
file:/C:/WebSphere/AppServer/profiles/AppSrv02/installedApps/kcgg1d8Nod
e02Cell/ClassloaderExample.ear/ClassloaderExampleWeb.war
类加载查看器会提供一个表,显示所有类加载器和每个加载器加载的类。同时这个表也显示出了委托模式:True表示类的加载方式是父加载优先(PARENT_FIRST);false表示类的加载方式是应用程序类加载优先(PARENT_LAST),或者在只有一个Web模块的情况下是WAR类加载,见图12-8:
图12-8类加载查看器结果表
正如所看到的,WAR类加载器已经装入示例中的servlet和VersionChecker类。类加载查看器还提供了查询功能:可以查找类、JAR文件、目录等等。如果你不知道类加载器加载了哪个类,这个功能就很有用。查询功能是大小写敏感但是允许使用通配符,比如使用*VersionChecker*查询VersionChecker类。
我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。
我原创,你原创,我们的内容世界才会更加精彩!
【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】
微信公众号
TechTarget
官方微博
TechTarget中国
作者
相关推荐
-
Portlet开发技能使Java专业人才需求巨增
Portlet机制越来越流行,越来越多的Java开发人员要求学习Portal API,那么它与servlet有什么异同?
-
构建高性能J2EE应用的十个技巧
构建高性能的J2EE应用不但需要了解常用的实施技巧。下面介绍最常用的10种有效方法,可帮助架构设计师们快速成为这方面的专家。
-
Tomcat 7 呼之欲出
随着去年12月 Java EE 6规格说明最终版本的确定,Apache Tomcat一直在努力开发Tomcat 7版本。在该版本中将支持Servlet 3.0和JSP 2.2。
-
JSF和Spring的集成
JSF和Spring集成的资料比较少,原理是获得彼此的上下文引用,以此进一步获得各自管理的bean,这是可能的,因为两者是web应用框架都遵循servlet……