如果仍然使用缺省的类加载策略:PARENT_FIRST, 由于BSF自身加载类,导致使用org.eclipse.osgi.framework.internal.core.BundleLoader装载器来装载,在这个装载器级别,似乎发现不了JavaScriptEngine类,实际上JavaScriptEngine和BSFManager同在一个包中。
一个可行的解决方法是,将该baf.jar(JavaScriptEngine类所在jar包)拷贝到jre/lib/ext目录下,则org.eclipse.osgi.framework.internal.core.BundleLoader类装载器一定能找到,因为该JDK加载顺序在org.eclipse.osgi.framework.internal.core.BundleLoader之先。
然而,系统管理员处于安全的考虑,不同意在jre下放置bsf.jar包。看来只得另寻他法。通过阅读下面的文章,理解WAS6.1的加载方式,可知创建应用程序共享库可以解决这一问题。参考:在应用程序服务器级别使用共享库。注意:在应用程序级别使用共享库并不能解决这个问题,如果该类没有使用自己的ClassLoader则应可行。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
12.5.2步骤2:添加一个EJB模块和工具JAR
下面,往应用程序中添加一个EJB,它也依赖VersionChecker JAR文件。在此,在EAR的根目录添加一个VersionCheckerV2.jar文件。在这个JAR文件中的VersionChecker类返回了Version 2.0。为了保证扩展类加载中的工具JAR可用,在EJB模块的manifest文件中添加一个引用,如例12-8:
例12-8更新EJB模块的MANIFEST.MF文件
Manifest-Version: 1.0
Class-Path: VersionCheckerV2.jar
现在的结果是:有一个Web模块,在它的WEB-INF/classes目录下面有一个servlet,在WEB-INF/lib目录下面有VersionCheckerV1.jar文件。还有一个EJB模块引用了EAR根目录下面的VersionCheckerV2.jar工具JAR。你期望Web模块装入VersionChecker类文件的版本是什么?是WEB-INF/lib下的Version 1.0还是工具JAR下面的Version 2.0?测试结果如例12-9:
例12-9类加载例2
VersionChecker called from Servlet
VersionChecker is v2.0.
Loaded bycom.ibm.ws.classloader.CompoundClassLoader@26282628
Local ClassPath:
C:\WebSphere\AppServer\profiles\AppSrv02\installedApps\kcgg1d8Node02Cel
l\ClassloaderExample.ear\ClassloaderExampleEJB.jar;C:\WebSphere\AppServ
er\profiles\AppSrv02\installedApps\kcgg1d8Node02Cell\ClassloaderExample
.ear\VersionCheckerV2.jar
Delegation Mode: PARENT_FIRST
VersionChecker called from EJB
VersionChecker is v2.0.
Loaded bycom.ibm.ws.classloader.CompoundClassLoader@26282628
Local ClassPath:
C:\WebSphere\AppServer\profiles\AppSrv02\installedApps\kcgg1d8Node02Cel l\ClassloaderExample.ear\ClassloaderExampleEJB.jar;C:\WebSphere\AppServ
er\profiles\AppSrv02\installedApps\kcgg1d8Node02Cell\ClassloaderExample
.ear\VersionCheckerV2.jar
Delegation Mode: PARENT_FIRST
正如所看到的,当同时调用EJB模块和Web模块,VersionChecker是Version 2.0。当然,原因是:WAR类加载器将请求委托给了父类加载器而不是他自己,所以工具JAR就被同一个类加载器加载,而无需考虑请求是来自于servlet还是EJB。
12.5.3步骤 3:改变WAR类加载的委托模式
现在是否希望Web模块使用WEB-INF/lib目录下面的VersionCheckerV1.jar文件?为了这个目的,需要先将类加载委托模式从parent first改为parent last。
设置委托模式为PARENT_LAST,使用如下步骤:
1. 在向导栏选择Enterprise Applications;
2. 选择ClassloaderExample应用程序;
3. 在模块部分选择Manage modules ;
4. 选择ClassloaderExampleWeb模块;
5. 将类加载顺序修改成应用程序类加载优先(PARENT_LAST)。记住,这个条目应该称为WAR类加载优先,参见 “类加载/委托模式”;
6. 单击OK.
7. 保存配置;
8. 重新启动应用程序。
WEB-INF/lib下的VersionCheckerV1返回version of 1.0。可以在例12-10中看到:
例12-10类加载例3
VersionChecker called from Servlet
VersionChecker is v1.0.
Loaded bycom.ibm.ws.classloader.CompoundClassLoader@4d404d40
Local ClassPath:
C:\WebSphere\AppServer\profiles\AppSrv02\installedApps\kcgg1d8Node02Cel
l\ClassloaderExample.ear\ClassloaderExampleWeb.war\WEB-INF\classes;C:\W
ebSphere\AppServer\profiles\AppSrv02\installedApps\kcgg1d8Node02Cell\Cl
assloaderExample.ear\ClassloaderExampleWeb.war\WEB-INF\lib\VersionCheck
erV1.jar;C:\WebSphere\AppServer\profiles\AppSrv02\installedApps\kcgg1d8
Node02Cell\ClassloaderExample.ear\ClassloaderExampleWeb.war
Delegation Mode: PARENT_LAST
VersionChecker called from EJB
VersionChecker is v2.0.
Loaded bycom.ibm.ws.classloader.CompoundClassLoader@37f437f4
Local ClassPath:
C:\WebSphere\AppServer\profiles\AppSrv02\installedApps\kcgg1d8Node02Cel
l\ClassloaderExample.ear\ClassloaderExampleEJB.jar;C:\WebSphere\AppServ
er\profiles\AppSrv02\installedApps \kcgg1d8Node02Cell\ClassloaderExample
.ear\VersionCheckerV2.jar
Delegation Mode: PARENT_FIRST
如果你使用类加载器的搜索功能,搜索*VersionChecker*,会得到图12-9:
图12-9类加载查看器搜索功能
例12-11显示源代码
例12-11类加载查看器搜索功能
WAS Module Compound Class Loader (WAR class loader):
file: / C: / WebSphere / AppServer / profiles / AppSrv02 /
installedApps / kcgg1d8Node02Cell / ClassloaderExample.ear /
ClassloaderExampleWeb.war / WEB-INF / lib / VersionCheckerV1.jar
WAS Module Jar Class Loader (Application class loader):
file: / C: / WebSphere / AppServer / profiles / AppSrv02 /
installedApps / kcgg1d8Node02Cell / ClassloaderExample.ear /
VersionCheckerV2.jar
12.5.4步骤4:使用共享库共享工具JAR
在此之前,只有一个应用程序使用VersionCheckerV2.jar文件。是否希望多个应用程序能够共享它?当然,你可以在每个EAR文件中把这个文件打包进去。但是如果需要修改这个工具JAR,那需要重新部署所有的应用程序。为了避免这个麻烦,你可以使用共享库全局共享这个JAR文件。
共享库可以定义在单元、节点、应用程序服务器和集群。一旦你定义了共享库,必须将它跟应用程序服务器的类加载器或者单独的Web模块关联起来。根据共享库指派的目的地不同,WebSphere会使用匹配的类加载器加载共享库。
只要愿意,可以定义多个共享库。也可以为应用程序、Web模块或者应用程序服务器指派多个共享库。
在应用程序级别使用共享库
定义一个名为VersionCheckerV2_SharedLib的共享库,并把它跟ClassloaderTest应用程序关联起来,步骤如下:
1. 在管理控制台,选择Environment→Shared Libraries;
2. 选择共享库的作用域,比如单元,单击New;
3. 如图12-10:
图12-10共享库配置
–Name: 输入VersionCheckerV2_SharedLib;
–Class path: 输入类路径中的条目,每个条目之间用回车隔开。如果提供绝对路径,建议是用WebSphere环境变量,比如%FRAMEWORK_JARS%/VersionCheckerV2.jar,确定你已经定义了一个和共享库相同作用域的变量。
–Native library path: 输入JNI代码使用的DLLs和.so文件列表。
4. 单击OK;
5.选择Applications → Enterprise Applications;
6. 选择应用程序ClassloadersExample ;
7. 在引用选项,选择Shared library references ;
8. 在应用程序列选择ClassloaderExample ;
9. 单击Reference shared libraries;
10. 选择VersionCheckerV2_SharedLib,单击>>按钮将选中的移动到Selected列,如下图12-11:
图12-11指定一个共享库
11.单击OK ;
12.ClassloaderExample应用程序共享库配置窗口如下图12-12:
图12-12将共享库指派给应用程序ClassloaderExample
13.单击OK,保存配置。
如果我们现在从EAR文件的根目录将VersionCheckerV2.jar删除,在EJB模块的manifest文件中也把引用删除,重新启动应用服务器,看到例12-12的结果。记住,Web模块的类加载顺序依然是应用程序类加载优先(PARENT_LAST)。
例12-12类加载例4
VersionChecker called from Servlet
VersionChecker is v1.0.
Loaded bycom.ibm.ws.classloader.CompoundClassLoader@2e602e60
Local ClassPath:
C:\WebSphere\AppServer\profiles\AppSrv02\installedApps\kcgg1d8Node02Cel
l\ClassloaderExample.ear\ClassloaderExampleWeb.war\WEB-INF\classes;C:\W
ebSphere\AppServer\profiles\AppSrv02\installedApps\kcgg1d8Node02Cell\Cl
assloaderExample.ear\ClassloaderExampleWeb.war\WEB-INF\lib\VersionCheck
erV1.jar;C:\WebSphere\AppServer\profiles\AppSrv02\installedApps\kcgg1d8
Node02Cell\ClassloaderExample.ear\ClassloaderExampleWeb.war
Delegation Mode: PARENT_LAST
VersionChecker called from EJB
VersionChecker is v2.0.
Loaded bycom.ibm.ws.classloader.CompoundClassLoader@19141914
Local ClassPath:
C:\WebSphere\AppServer\profiles\AppSrv02\installedApps\kcgg1d8Node02Cel
l\ClassloaderExample.ear\ClassloaderExampleEJB.jar;C:\henrik\VersionChe
ckerV2.jar
Delegation Mode: PARENT_FIRST
正如预料的,由于Web模块的委托模式,当servlet需要VersionChecker类,VersionCheckerV1.jar文件被加载。当EJB需要VersionChecker的类的时候,就会从指向C:\henrik\VersionCheckerV2.jar的共享库中加载它。如果你希望Web模块也使用共享库,只需要将类加载顺序恢复成缺省值,Web模块的类加载是父类加载优先。
在应用程序服务器级别使用共享库
共享库也可以跟应用程序服务器关联起来。在这个服务器上的部署的所有应用程序都能够看到共享库的代码列表。要把共享库跟应用程序服务器关联起来,首先要为应用程序服务器创建一个附加的类加载器,步骤如下:
1. 选择应用程序服务器;
2. 在应用程序基础结构部分,展开Java and Process Management,选择Class loader;
3. 选择New,为这个类加载器选择类加载顺序,父类加载优先(PARENT_FIRST)或者应用程序类加载优先(PARENT_LAST),单击Apply;
4. 单击刚刚创建的类加载器;
5. 单击Shared library references;
6. 单击Add,选择希望跟应用程序服务器关联的库。重复选择操作,将多个库跟这个类加载器关联。比如选择VersionCheckerV2_SharedLib条目;
7. 单击OK;
8. 保存配置;
9. 重新启动应用程序服务器,修改才会生效。
将VersionCheckerV2共享库跟应用程序服务器关联起来,就得到例12-13的结果。
例12-13类加载例5
VersionChecker called from Servlet
VersionChecker is v1.0.
Loaded bycom.ibm.ws.classloader.CompoundClassLoader@40c240c2
Local ClassPath:
C:\WebSphere\AppServer\profiles\AppSrv02\installedApps\kcgg1d8Node02Cel
l\ClassloaderExample.ear\ClassloaderExampleWeb.war\WEB-INF\classes;C:\W
ebSphere\AppServer\profiles\AppSrv02\installedApps\kcgg1d8Node02Cell\Cl
assloaderExample.ear\ClassloaderExampleWeb.war\WEB-INF\lib\VersionCheck
erV1.jar;C:\WebSphere\AppServer\profiles\AppSrv02\installedApps\kcgg1d8
Node02Cell\ClassloaderExample.ear\ClassloaderExampleWeb.war
VersionChecker called from EJB
VersionChecker is v2.0.
Loaded bycom.ibm.ws.classloader.ExtJarClassLoader@7dee7dee
Local ClassPath: C:\henrik\VersionCheckerV2.jar
Delegation Mode: PARENT_FIRST
我们定义的新的名为ExtJarClassLoader类加载器,在EJB模块请求时,它装入VersionCheckerV2.jar文件。由于委托模式,WAR类加载器继续装入自己的version。
12.6类加载器问题诊断
JVM 5.0提供了一些配置,可以让我们查看详细的类装入,比如JVM参数 -verbose:dynload、-Dibm.cl.verbose=<name>。
在实际开发过程中,如果使用不当,会出现很多类加载相关的问题。当遇到类加载问题时,可以查看WAS的相关日志,在日志中出现如下异常,可以认为是类加载器出现了问题:
ClassCastException
ClassNotFoundException
NoClassDefFoundError
NoSuchMethodError
IllegalArgumentException
UnsatisfiedLinkError
VerifyError
关于问题诊断,将在下一篇文章《WAS 6.1类加载问题诊断》详细阐述。
总结
本文针对WAS6.1版本,详细介绍了类加载的概念以及如何客户化,并通过几个例子向大家讲述了影响类加载的选项的使用。虽然WAS 6.1允许根据需要修改类加载策略,比如将父类优先改成应用程序优先,但是不推荐这么使用。笔者曾经就遇到因为修改策略,导致应用程序无法启动。原因是WAS中 的组件和应用程序使用的某些类是一致的,加载策略选择不正确,就会导致类加载错误。