|
将冲突jar包打包到Web模块中,并设置相应Web模块的类加载器的委托模式为Parent_Last,应用程序在运行过程中加载类的时候,这个Web模块的类加载器会首先查找 WebContent/WEB-INFO/lib目录下的jar包进行类的加载;而对于其它的Web模块,由于其类加载器的委托模式仍然为缺省的Parent_First,它们的类加载器仍然首先从应用程序的共享库或者WebSphere的共享路径上加载jar包中的类,从而解决了jar包冲突的问题。
命令行运行方式解决jar包冲突
不论是设置共享库,还是将冲突jar包打包到应用程序中,其解决的问题都是在应用程序的一个Web模块中只使用了冲突jar包的一个版本的情况。我们在开发中曾经遇到过这样的情况:应用程序的Web模块中已经使用了1.4版本的xerces.jar,由于Web功能的扩展,在这个模块中又引入一个新的第三方工具,而这个第三方工具需要使用2.0版本的xerces.jar才能正常工作,这种情况下的jar包冲突如何解决呢?
在前面类加载器的部分已经介绍过,每个应用程序的一个Web模块最多只能有一个类加载器,而Web模块的类加载器中加载的类的生命周期为整个应用程序的运行期,也就是说,Web模块加载器不可能同时加载一个类的两个版本,同时,Web模块的类加载器的委托模式也是在应用程序运行前设置的,在应用程序运行期内无法改变的,因此,上面描述的在一个Web模块中同时使用两个版本的jar包的问题,象前两种方法那样配置运行在一个JVM内的类加载器的设置的方法是无法解决的。
唯一的解决办法就是在应用程序运行的JVM外,启动另外一个JVM来运行调用冲突jar包的代码,因为两个不同的JVM可以加载各自的类,从而解决jar包冲突问题。
这种情况下,原来使用jar包的老版本的方式(包括jar包放置路径,共享库设置方式,类加载器的委托模式等)不变,将对jar包新版本的调用通过命令行运行方式实现。具体做法是:将对jar包新版本内功能的调用,封装到一个可以单独运行的类中,在Web模块中以命令行方式运行这个类。同时把这个类以及jar包的新版本放在任意一个was可访问的路径上(比如/usr/WebSphere),在命令行的classpath参数中包含这个路径(比如/usr/WebSphere)。
下面通过举例说明命令行运行方式的编程过程,在本例中,假设TestEar应用程序的Web模块TestWar中,已经使用了conflict_v1.jar,由于新添功能需要使用conflict_v2.jar中的exampleCall()功能。
冲突jar包conflict_v2.jar提供的功能:
代码1:冲突jar包conflict_v2.jar功能
Package com.ibm.conflict;Public class ConflictClass{ …….Public static String exampleCall(string param){ String rs; ……; Return rs;}……}
不存在冲突问题时的编码举例:
如果没有jar包冲突问题,则对这个功能的调用是简单的,只需要将conflict_v2.jar放在应用程序自身或者其父类加载器的查找路径上,然后在Web模块中直接调用即可,如下:
代码2:不存在冲突时的调用方式
Public String methodA(String param){ …… String rs = ConflictClass.exampleCall(param); …… Return rs;}
存在冲突后的命令行运行方式编码举例
针对jar包冲突问题,我们需要在Web模块中做如下的修改:
步骤一:将冲突jar包放在was可访问的路径上,比如/usr/WebSphere/conflict_v2.jar。
步骤二:将对包含冲突代码的调用封装到一组可单独运行的类中,它们将调用冲突jar包的功能,并将结果以系统输出的方式打印到系统标准输出上。 将这些类封装到一个单独的jar文件中,比如workAroundConflict.jar,并将其放在was可访问的路径上,比如/usr/WebSphere/workAroundConflict.jar。
Package com.ibm.test;Import com.ibm.ConflictClass;Public class WorkAround{Public static void main(String[] args){ String param1=args[0]; String returnStr=ConflictClass.exampleCall (); System.out.println("<RTStr>"+returnStr+"</RTStr>" ; Return;}}
代码3:将对冲突代码的调用写入一个单独的类WorkAround
步骤三:在Web模块中通过命令行方式调用封装的类,通过classpath指定所有依赖的jar包和类路径。运行封装类,从系统标准输出中取得运行结果。
Public static String methodA (String param){ …… String rtStr = ""; String lStr="<RTStr>"; String rStr="<RTStr>"; //put all the dependency jar here String classPath="/usr/WebSphere/conflict_v2.jar; /usr/WebSphere/workaroundConflict.jar;……"; String className="com.ibm.test.WorkAround"; String cmdLine="java -classpath " +classPath +" " +className + " "+ param; Try{ Process process = Runtime.getRuntime().exec(cmdLine,null); process.waitFor(); BufferedReader br= new BufferedReader( new InputStreamReader(process.getInputStream())); while ((s = br.readLine())!=null) { if (null == out) out=s; else out+=s;}//get result from outif (null != out){ int lIndex = out.lastIndexOf(lStr); int rIndex = out.lastIndexOf(rStr); rsStr = out.substring(lIndex+lStr.length, rIndex);} } catch (Exception e){ e.printStackTrace();}…….return rsStr;}
代码4:在应用程序中通过命令行方式运行WorkAround
命令行运行方式通过启动另外一个JVM的方式运行冲突代码,在一个不同的JVM中加载冲突的类,从而解决了jar包冲突问题。
但是命令行运行方式毕竟不是一个很好的方式,它存在以下的弊端:
命令行运行方式只适用于对冲突jar包的使用只是运行一段代码,或者只要求返回简单的字符串结果的情况。对于复杂的交互,命令行方式无法做到。但是如果在别无他法的情况下,可以适当地划分封装对冲突代码调用的jar包的包含范围,尽量将命令行运行的代码接口简单化。
命令行运行方式因为启动了另外一个JVM来运行,降低了WebSphere的性能。
因此,命令行方式只适用于一些极为特殊的情况下解决jar包冲突问题。
结论
本文对基于WebSphere的大型项目开发中遇到的jar包冲突问题,结合WebSphere中类加载器的概念,给出了三种解决办法以及相应的操作步骤和实现代码,并分析了各种方式所适用的具体情况。
项目开发过程中的jar包冲突,主要存在以下三种情况:
多个应用程序间的jar包冲突
应用程序内多个Web模块间的jar包冲突
应用程序内同一个Web模块内部jar包冲突
通过对类加载器的分析我们知道,解决jar包冲突问题的根本在于合理配置类加载器。在深入理解WebSphere中类加载器的层次结构的基础上,我们给出了"共享库解决jar包冲突"以及"打包到Web模块中解决jar包冲突"的办法,通过合理地配置WebSphere中类加载器及其委托模式,可以解决大多数的jar包冲突问题。但是由于这个层次结构中类加载器只配置到Web模块,因此,对于Web模块内部的jar包冲突问题,类加载器的配置是无法解决的,为此我们给出了"命令行运行方式解决jar包冲突"的办法。
表2中列出了在不同的WAS版本下,三种解决jar包冲突问题的办法所适用的jar包冲突情况。
表2:Jar包冲突解决办法适用的jar包冲突情况总结 |
|