如何在 WebSphere 中解決 jar 包沖突
簡介:
Jar 包沖突問題是在大型 Java 軟件開發中經常遇到的問題,系統開發人員經常會為解決類似的問題耗費大量的時間進行調試和測試,本文根據各種際情況,結合 WebSphere 中類加載器,討論了幾種解決 jar 包沖突問題的辦法,并給出了具體實現的步驟及源代碼。
讀者定位為具有 Java 和 WebSphere 開發經驗的開發人員。
讀者可以學習到在 WebSphere 中類加載器的定義以及解決 jar 包沖突問題的幾種辦法,并可以直接使用文章中提供的 Java 代碼,從而節省他們的開發和調試時間,提高效率。
大型的基于 WebSphere 的項目開發中,同一個 WebSphere Application Server(以下簡稱 WAS)上會部署多個應用程序,而這多個應用程序必然會共用一些 jar 包,包括第三方提供的工具和項目內部的公共 jar 等。把這些共用的 jar 包提取出來在多個應用程序之間共享,不僅可以統一對這些 jar 包進行維護,同時也提高了 WAS 的性能。但是隨著應用的不斷擴大,新的應用程序的不斷增加,新的應用程序會希望使用一些更高版本的共享 jar 包,而由于系統運行維護的需要,老的應用程序仍然希望用老版本的共享 jar 包,這樣就必然造成了共享 jar 包的版本沖突。jar 包版本沖突問題是在大型應用項目的開發中經常遇到的問題,本文試圖從 WebSphere 的類加載器入手,討論幾種在不同情況下解決 jar 包沖突問題的辦法。
Jar 包沖突實際上是應用程序運行時不能找到真正所需要的類,而影響類的查找和加載的是 JVM 以及 WebSphere 中的類加載器(class loader),為此,我們首先介紹一下 WebSphere 中的類加載器以及一些相關的概念。
Java 應用程序運行時,在 class 執行和被訪問之前,它必須通過類加載器加載使之有效,類加載器是 JVM 代碼的一部分,負責在 JVM 虛擬機中查找和加載所有的 Java 類和本地的 lib 庫。類加載器的不同配置影響到應用程序部署到應用程序服務器上運行時的行為。JVM 和 WebSphere 應用程序服務器提供了多種不同的類加載器配置, 形成一個具有父子關系的分層結構。WebSphere 中類加載器的層次結構圖 1 所示:
圖 1:WebSphere 中類加載器的層次結構

如上圖所示,WebSphere 中類加載器被組織成一個自上而下的層次結構,最上層是系統的運行環境 JVM,最下層是具體的應用程序,上下層之間形成父子關系。
- JVM Class loader:位于整個層次結構的最上層,它是整個類加載器層次結構的根,因此它沒有父類加載器。這個類加載器負責加載 JVM 類, JVM 擴展類,以及定義在 classpath 環境變量上的所有的 Java 類。
- WebSphere Extensions Class loader:WebSphere 擴展類加載器 , 它將加載 WebSphere 的一些 runtime 類,資源適配器類等。
- WebSphere lib/app Class loader:WebSphere 服務器類加載器,它將加載 WebSphere 安裝目錄下 $(WAS_HOME)/lib/app 路徑上的類。 在 WAS v4 版本中,WAS 使用這個路徑在所有的應用程序之間共享 jar 包。從 WAS v5 開始, 共享庫功能提供了一種更好的方式,因此,這個類加載器主要用于一些原有的系統的兼容。
- WebSphere "server" Class loader:WebSphere 應用服務器類加載器。 它定義在這個服務器上的所有的應用程序之間共享的類。WAS v5 中有了共享庫的概念之后,可以為應用服務器定義多個與共享庫相關聯的類加載器,他們按照定義的先后順序形成父子關系。
- Application Module Class Loader:應用程序類加載器,位于層次結構的最后一層,用于加載 J2EE 應用程序。根據應用程序的類加載策略的不同,還可以為 Web 模塊定義自己的類加載器。
關于 WebSphere 的類加載器的層次結構,以下的幾點說明可能更有助于進一步的理解類的查找和加載過程:
- 每個類加載器負責在自身定義的類路徑上進行查找和加載類。
- 一個子類加載器能夠委托它的父類加載器查找和加載類,一個加載類的請求會從子類加載器發送到父類加載器,但是從來不會從父類加載器發送到子類加載器。
- 一旦一個類被成功加載,JVM 會緩存這個類直至其生命周期結束,并把它和相應的類加載器關聯在一起,這意味著不同的類加載器可以加載相同名字的類。
- 如果一個加載的類依賴于另一個或一些類,那么這些被依賴的類必須存在于這個類的類加載器查找路徑上,或者父類加載器查找路徑上。
- 如果一個類加載器以及它所有的父類加載器都無法找到所需的類,系統就會拋出 ClassNotFoundExecption 異常或者 NoClassDefFoundError 的錯誤。
類加載器有一個重要的屬性:委托模式(Delegation Mode,有時也稱為加載方式:Classloader mode)。委托模式決定了類加載器在查找一個類的時候, 是先查找類加載器自身指定的類路徑還是先查找父類加載器上的類路徑。
類加載器的委托模式有兩個取值:
- Parent_First:在加載類的時候,在從類加載器自身的類路徑上查找加載類之前,首先嘗試在父類加載器的類路徑上查找和加載類。
- Parent_Last:在加載類的時候,首先嘗試從自己的類路徑上查找加載類,在找不到的情況下,再嘗試父類加載器類路徑。
有了委托模式的概念,我們可以更加靈活的配置在類加載器的層次結構中類的加載和查找方式。表 1 中給出了在 WebSphere 的類加載器層次結構中各個類加載器的委托模式的定義,并給出了不同的類加載器內類的生命周期。

注意:在上表中,"JVM Class loader" 因為在類加載器的最頂層,它沒有父類加載器,因此其委托模式為 N/A,"WebSphere Extensions Class loader"和"WebSphere lib/app Class loader"的委托模式固定為表中的取值,不可配置,其它的類加載器的委托模式都是可以配置的。
WebSphere 中對類加載器有一些相關的配置,稱為類加載器策略(class loader policy)。類加載器策略指類加載器的獨立策略(class loader isolation policy), 通過類加載器策略設置,我們可以為 WAS 和應用程序的類加載器進行獨立定義。
每個 WAS 可以配置自己的應用程序類加載器策略,WAS 中的每個應用程序也可以配置自己的 Web 模塊類加載器策略,下面我們對這兩種策略分別介紹。
1 .應用服務器(WAS)配置:應用程序類加載器策略
應用服務器對應用程序類加載器策略有兩種配置:
- Single:整個應用服務器上的所有應用程序使用同一個類加載器。在這種配置下,每個應用程序不再有自己的類加載器。
- Multiple:應用服務器上的每個應用程序使用自己的類加載器。
2 .應用程序配置:Web 模塊類加載器策略
應用程序中對 Web 模塊類加載器有兩種配置:
- Application:整個應用程序內的所有的實用程序 jar 包和 Web 模塊使用同一個類加載器。
- Module:應用程序內的每個 Web 模塊使用自己的類加載器。應用程序的類加載器仍然存在,負責加載應用程序中 Web 模塊以外的其它類,包括所有的實用程序 jar 包。
從上面的定義可以看出,不同的類加載器策略的配置下,類加載器的層次結構上的某些類加載器可能不存在。比如在應用程序服務器的應用程序類加載 器策略定義為 single 的情況下,應用程序的類加載器將不存在,同一個應用服務器上的所有應用程序將共用同一個類加載器,這也就意味著不同的應用程序之間的類是共享的,應用程序 間不能存在同名的類。
Jar 包沖突問題實際上就是應用程序希望用某一個確定版本的 jar 包中的類,但是類加載器卻找到并加載了另外一個版本的 jar 包中的類。在上一部分介紹了 WebSphere 中類加載器的基本概念和相關配置之后,我們來看如何在 WebSphere 中解決 jar 包沖突。
在 WAS v5 版本之前,使用共享 jar 包的方式是將 jar 包放在 $(WAS_HOME)/lib/app 路徑下,從上一部分中,我們可以看到,這個路徑正是"WebSphere lib/app Class loader" 類加載器的類查找路徑,WebSphere 會查找這個路徑以取得相應得 jar 包中的 Java 類,從而做到在 WebSphere ND 上的多個應用程序之間共享 jar 包的目的。但是這樣做的一個缺點就是這些共享 jar 包暴露給 WebSphere ND 上所有的應用程序,對于那些希望使用 jar 包其它版本的應用程序,這些 jar 包也同樣存在在了它們的類加載器類路徑上,因此,就不可避免的會造成版本的沖突。在 WAS v5 版本及之后,增加了共享庫(shared library)的概念,推薦的在多個應用程序間共享 jar 包并避免 jar 包沖突的方式是使用共享庫。
具體分析引起 jar 包沖突的情況,主要有三種:
- 多個應用程序間 jar 包沖突:多個應用程序間由于使用了共享 jar 包的不同版本而造成 jar 包版本沖突。
- 應用程序中多個 Web 模塊間 jar 包沖突:同一個應用程序內部,不同的 Web 模塊間同時使用一個 jar 包的不同版本而造成 jar 包版本沖突。
- 應用程序中同一個 Web 模塊內 jar 包沖突:同一個應用程序內部,同一個 Web 模塊內,由于需要同時使用同一個 jar 包的兩個版本而造成的 jar 包沖突
本部分根據這三種 jar 包沖突的情況,討論三種解決 jar 包沖突的辦法,并具體討論三種解決辦法的實現步驟和適用情況:
- 共享庫方式解決 jar 包沖突:主要解決應用程序間的 jar 包沖突問題
- 打包到 Web 模塊中解決 jar 包沖突:主要解決應用程序中多個 Web 模塊間 jar 包沖突問題
- 命令行運行方式解決 jar 包沖突:主要解決應用程序中同一個 Web 模塊內 jar 包沖突問題
在 WAS v5 中,提供了一種很好的機制,使得 jar 包只存在于需要這個 jar 包的應用程序的類加載器的路徑上,而其它的應用程序不受它的任何影響,這就是共享庫(Shared library)。共享庫可以用在應用服務器級別和應用程序級別,使用應用程序級別的共享庫,其好處就是在不同的應用程序之間使用共享 jar 包的不同版本。我們可以為一些通用 jar 包的每個不同版本定義成不同的共享庫,應用程序希望使用哪個版本,就把這個版本的共享庫放到應用程序的類加載器的類路徑上,這種方式有效的解決了應用程序 之間 jar 包沖突的問題。
下面舉例介紹定義和使用共享庫的具體方法,本例中,假設存在 xerces.jar 包版本沖突。
1 . 定義共享庫
系統管理員可以在 WebSphere 的 Admin console 中定義共享庫,可以分別在 Cell、Node 以及 server 的級別上定義。
步驟一 : 進入 Admin console,選擇 Environment > Shared Library > new。如圖 2 所示:
圖 2:WebSphere Admin Console 中進入共享庫頁面步驟二: 給出共享庫的名字,并指定共享的文件和目錄。多個不同的文件 / 目錄之間通過"Enter"鍵分隔,且不能有路徑分隔符,如":"和";"等。如圖 3 所示:
圖 3:WebSphere Admin Console 中添加共享庫步驟三:點擊 Apply 或者 OK 之后,就添加了一個名字為 Xerces V2.0 的共享庫。記住添加完成后一定要在 admin console 保存配置。如圖 4 所示:
圖 4:WebSphere Admin Console 中共享庫列表-
2 .安裝應用程序
進入 Admin console,選擇 Applications > Install New Application 安裝應用程序。請參照 IBM WebSphere 的 Admin console 使用手冊進行安裝新的應用程序,此處不再詳細介紹。
3 .將共享庫關聯到應用程序
步驟一:進入 Admin console,選擇 Applications > Enterprise applications ,并選擇需要使用共享庫的應用程序。注意:因為要改變應用程序的設置,所以如果應用程序已經運行,需要先停掉應用程序。如圖 5 所示:
圖 5:WebSphere Admin Console 中選擇需要配置的應用程序步驟二 : 點擊應用程序,進入后,選擇 Libraries。如圖 6 所示:
圖 6:WebSphere Admin Console 中選擇應用程序庫屬性步驟三 : 點擊 Add,為應用程序添加共享庫。如圖 7 所示:
圖 7:WebSphere Admin Console 中應用程序添加庫步驟四 : 從下拉列表中選擇所需要的共享庫,點擊 OK。如圖 8 所示:
圖 8:WebSphere Admin Console 中應用程序添加庫頁面指定所用的共享庫
這樣,Xerces V2.0 共享庫定義的 xerces 版本就存在于了應用程序類加載器的類加載路徑上。注意,在添加完成后要保存服務器的設置。
4 .設置應用程序的類加載器的委托模式為 Parent_Last
為了進一步防止共享庫定義的 jar 包的其它版本已經存在于 JVM 或者 WebSphere 的類加載器路徑上,還需要設置應用程序的類加載器的委托方式為 Parent_Last。
步驟一:進入 Admin console,選擇 Applications > Enterprise applications > ,選擇需要配置的應用程序。如圖 9 所示:
圖 9:WebSphere Admin Console 中選擇需要配置的應用程序步驟二:點擊進入應用程序,設置類加載器的委托模式為 Parent_Last。注意:在配置完成后,要保存配置,最后啟動應用程序。如圖 10 所示:
圖 10:WebSphere Admin Console 中為應用程序設置類加載器委托模式
通過上面的配置,即使 xerces 的其它版本已經存在于系統中,應用程序在運行時,其類加載器也會首先查找并加載指定的共享庫中的 xerces 版本。這樣我們就通過使用共享庫的方式,解決了 jar 包版本沖突問題。
共享庫的方式,只是在應用程序的層次上,在多個應用程序之間解決了共享 jar 包造成的版本沖突問題,如果一個應用程序的內部,其中一個 Web 模塊使用了一個 jar 包的 A 版本,而另一個 Web 模塊使用這個 jar 包的 B 版本,在這種情況下造成的 jar 包沖突,共享庫的方式是無法解決的,我們可以考慮將其中一個在多個應用程序間共享的 jar 包版本,比如 A 版本,定義成共享庫,或者放在"WebSphere lib/app Class loader"類加載器路徑上供多個應用程序使用,而將 B 版本的 jar 包打包到使用它的 Web 模塊中的方式來解決沖突。
其次,目前很多在線的系統是 WAS v4 的遺留系統,其上運行的應用程序已經使用了"WebSphere lib/app Class loader"類加載器,將 jar 包放在 $(WAS_HOME)/lib/app 目錄下進行共享。如果由于其中某個應用程序的升級或者新增加某個應用程序,需要使用某個共享 jar 包的其它版本,在這種情況下,為了減少對系統的影響,也可以考慮將這個共享 jar 包的新版本打包到升級(或新增)的應用程序中的方式來解決 jar 包沖突。
由于 Web 模塊的 WebContent/WEB-INFO/lib 目錄在應用程序 Web 模塊的類加載器查找路徑上,因此,我們可以把 jar 包放在這個目錄下,Web 模塊的類加載器將自動查找并加載這個 jar 包中的類。
步驟一:在 WSAD IE 集成開發環境中,將沖突 jar 包放在 Web 模塊的 WebContent/WEB-INFO/lib 目錄下。如圖 11 所示:
圖 11:WSAD IE 中為 Web 模塊添加庫步驟二:在 Admin console 中,將應用程序部署到 WebSphere server 上之后,進入 Applications > Enterprise Applications, 選擇相應的應用程序,并確認應用程序不在運行狀態 ( 參見前面章節中選擇應用程序的步驟 )。點擊進入應用程序,確認應用程序的類加載器的委托模式為 Parent_First, 應用程序的類加載器策略為 Module。如圖 12 所示:
圖 12:WebSphere Admin Console 中應用程序屬性配置頁面步驟三:在同一個頁面上,選擇 Web Modules,點擊進入。如圖 13 所示:
圖 13:WebSphere Admin Console 中選擇應用程序 Web 模塊屬性步驟四:點擊相應的包含沖突 jar 包的 Web 模塊,設置 Web 模塊的類加載器的委托模式為 Parent_Last。注意:在設置完成后要保存服務器配置,并啟動應用程序。如圖 14 所示:
圖 14:WebSphere Admin Console 中為 Web 模塊指定類加載器委托模式
將沖突 jar 包打包到 Web 模塊中,并設置相應 Web 模塊的類加載器的委托模式為 Parent_Last,應用程序在運行過程中加載類的時候,這個 Web 模塊的類加載器會首先查找 WebContent/WEB-INFO/lib 目錄下的 jar 包進行類的加載;而對于其它的 Web 模塊,由于其類加載器的委托模式仍然為缺省的 Parent_First,它們的類加載器仍然首先從應用程序的共享庫或者 WebSphere 的共享路徑上加載 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 功能
2
3 Public class ConflictClass{
4 …… .
5 Public static String exampleCall(string param){
6 String rs;
7 …… ;
8 Return rs;
9 }
10 ……
不存在沖突問題時的編碼舉例:
如果沒有 jar 包沖突問題,則對這個功能的調用是簡單的,只需要將 conflict_v2.jar 放在應用程序自身或者其父類加載器的查找路徑上,然后在 Web 模塊中直接調用即可,如下:
代碼 2:不存在沖突時的調用方式
2 ……
3 String rs = ConflictClass.exampleCall(param);
4 ……
5 Return rs;
6 }
存在沖突后的命令行運行方式編碼舉例
針對 jar 包沖突問題,我們需要在 Web 模塊中做如下的修改:
- 步驟一:將沖突 jar 包放在 was 可訪問的路徑上,比如 /usr/WebSphere/conflict_v2.jar。
步驟二:將對包含沖突代碼的調用封裝到一組可單獨運行的類中,它們將調用沖突 jar 包的功能,并將結果以系統輸出的方式打印到系統標準輸出上。 將這些類封裝到一個單獨的 jar 文件中,比如 workAroundConflict.jar,并將其放在 was 可訪問的路徑上,比如 /usr/WebSphere/workAroundConflict.jar。
1 Package com.ibm.test;
2
3 Import com.ibm.ConflictClass;
4
5 Public class WorkAround{
6 Public static void main(String[] args){
7 String param1=args[0];
8 String returnStr=ConflictClass.exampleCall ();
9 System.out.println("<RTStr>"+returnStr+"</RTStr>");
10 Return;
11 }
12 }
代碼 3:將對沖突代碼的調用寫入一個單獨的類 WorkAround步驟三:在 Web 模塊中通過命令行方式調用封裝的類,通過 classpath 指定所有依賴的 jar 包和類路徑。運行封裝類,從系統標準輸出中取得運行結果。
1 Public static String methodA (String param){
2 ……
3 String rtStr = "";
4 String lStr="<RTStr>";
5 String rStr="<RTStr>";
6 //put all the dependency jar here
7 String classPath=
8 "/usr/WebSphere/conflict_v2.jar;
9 /usr/WebSphere/workaroundConflict.jar; ……";
10 String className="com.ibm.test.WorkAround";
11 String cmdLine="java -classpath " +classPath +" " +className + " "+ param;
12 Try{
13 Process process = Runtime.getRuntime().exec(cmdLine,null);
14 process.waitFor();
15 BufferedReader br= new BufferedReader(
16 new InputStreamReader(process.getInputStream()));
17 while ((s = br.readLine())!=null) {
18 if (null == out)
19 out=s;
20 else
21 out+=s;
22 }
23 //get result from out
24 if (null != out){
25 int lIndex = out.lastIndexOf(lStr);
26 int rIndex = out.lastIndexOf(rStr);
27 rsStr = out.substring(lIndex+lStr.length, rIndex);
28 }
29
30 } catch (Exception e){
31 e.printStackTrace();
32 }
33 …… .
34 return rsStr;
35 }
代碼 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 包沖突情況總結

- IBM WebSphere Application Server V5 ClassloaderNamingSpace Guide。
- IBM WebSphere Application Server V5.1 System Management and Configuration。
常紅平是一位 IBM CSDL 的軟件工程師,IBM certified DB2 DBA 和 IBM certified DB2 developer。他目前正在從事企業電子商務應用的開發。您可以通過 changhp@cn.ibm.com 和他聯系。