概述
Java Classloader長期以來一直是導致混亂的根源,并且隨著 Java 語言的發展變得比以往任何時候都更為復雜。隨著 J2EE 應用服務器的出現,Java Classloader的復雜性進一步增加了。現在,JVM 中的每個應用程序都可能有自己的Classloader層次,這將導致一個單獨的 JVM 可能包含許多的Classloader。而不同的J2EE應用服務器的Classloader的層次和策略也會有所差異,移植的復雜度也隨之上升。
第一部分曾經提到:在移植的前期,我們應當分析系統的框架,澄清其中各個模塊之間的依賴關系(包括各個業務模塊之間的依賴關系,以及Web模塊和EJB模塊之間的依賴關系等),為了盡量避免模塊之間的交叉引用,我們甚至要重新打包。大家可能會問,我們的應用程序原本在WebLogic 、Tomcat、Jboss、Resin上能夠正常的運行,為什么我們要重新分析模塊之間的依賴關系,甚至重新打包呢?實際上,每個應用服務器都有著對于ClassLoader的不同實現。由于這些ClassLoader在裝載類文件時的行為會有所差異,所以如果應用程序未經重整和優化,很有可能在WebSphere應用服務器上運行失敗。然而,面臨這種差異,我們并不需要過多的恐慌。實際上,只要把握以下幾個原則,相信,交織在模塊依賴和類型裝載之中的問題,就可以迎刃而解了:
一、 理解WebSphere ClassLoader的裝載層次和裝載策略
二、 理清模塊間的依賴關系
三、 將合適的裝載策略應用到各個模塊
注意:我們并不需要了解WebLogic、Tomcat、JBoss、Resin等J2EE應用服務器的 Classloader的體系結構,只要我們理解了WebSphere的Classloader的體系結構,理清了應用程序的各個模塊之間的依賴關系,就可以解決由不同的J2EE應用服務器的Classloader的體系結構的差異給移植帶來的問題。
首先我們簡單介紹一下WebSphere ClassLoader的體系結構,詳細內容可參看參考文檔。
圖一、WebSphere Classloader層次結構圖
處于體系結構最上層的是系統 Classloader,它由bootstrap,extensions和 classpath三個classloader組成,bootstrap classloader會查找jre/lib下的類文件,從而加載核心類庫,extensions classloader使用系統屬性java.ext.dirs(通常是jre/lib/ext)查找和加載類文件,classpath classloader使用操作系統的classpath環境變量來查找和加載類文件。
處于體系結構第二層的是WebSphere Extensions Classloader,它被用于裝載WebSphere的運行時類庫、J2EE類庫以及用戶代碼。它使用系統屬性ws.ext.dirs(通常是%was_root%\classes,%was_root%\lib和%was_root%\lib\ext)查找和加載類文件。
處于體系結構第三層的是Application Classloader,用于加載EARs,RARs,JARs中的類文件。由于Application Classloader Policy是MULTIPLE,WebSphere應用服務器會為每一個EAR文件(EAR 1到EAR N)分配了一個Application Classloader。值得注意的是,如果WAR Classloader Policy如果設置為Application,那么Application Classloader可用于加載WARs中的類文件。
處于體系結構最底層的是WAR Classloader,用于加載WAR文件中的類文件。圖中示意了WebSphere應用服務器為每一個WAR文件(WAR 1到 WAR N)分配了一個WAR Classloader的場景。值得注意的是,如果WAR Classloader Policy設置為MODULE,WebSphere應用服務器才會使用WAR Classloader加載WAR文件中的類文件。
圖一中的單向箭頭表明了引用次序,即處于下面的Classloader可以調用其上層的類文件,而上層的Classloader無法調用其下面的類文件。這就為應用程序及其各個模塊的組織結構和設置Classloader策略提供了線索和限制。
接下來,我們用一個實例來說明針對特定的模塊依賴關系應當如何設置WebSphere Classloader的裝載策略(policy)以及如何將各個模塊部署到合適的位置。
有一個應用程序里有三個互相依賴的模塊,其調用關系如下:
圖二、模塊關系圖
其中,WAR模塊中的SampleServlet類調用了UTILITY模塊中SampleUtility類,如代碼一所示:
代碼一、SampleServlet.java
public class SampleSevlet extends HttpServlet implements Servlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("SampleServlet invokes SampleUtility");
new SampleUtility().callSampleSessionBean();
}
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
}
}
|
UTILITY模塊中SampleUtility類調用了EJB模塊中的SampleSessionBean類(通過callSampleSessionBean方法),如代碼二所示:
代碼二、SampleUtility.java
public class SampleUtility {
public void callSampleSessionBean() {
try{
Context initCtx = new InitialContext();
Object result = initCtx.lookup("ejb/sample/SampleSessionHome");
SampleSessionHome ssh = (SampleSessionHome)
PortableRemoteObject.narrow(
result, SampleSessionHome.class);
SampleSession ss = ssh.create();
System.out.println("SampleUtility invokes SampleSessionBean");
ss.callSampleUtility();
}catch(Exception e){
e.printStackTrace();
}
}
public void finish() {
System.out.println("The process has been finished");
}
}
|
SampleSessionBean類又調用了UTILITY模塊中的SampleUtility類,如代碼三所示:
代碼三、SampleSessionBean.java
public class SampleSessionBean implements javax.ejb.SessionBean {
private javax.ejb.SessionContext mySessionCtx;
public javax.ejb.SessionContext getSessionContext() {
return mySessionCtx;
}
public void setSessionContext(javax.ejb.SessionContext ctx) {
mySessionCtx = ctx;
}
public void ejbCreate() throws javax.ejb.CreateException {
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void ejbRemove() {
}
public void callSampleUtility() {
System.out.println("SampleSessionBean invokes SampleUtility");
new SampleUtility().finish();
}
}
|
這三個類之間的調用關系如下圖所示:
圖三、順序圖
下面我們通過不同的部署策略來深入探討WebSphere Classloader是如何影響應用程序運行的。1、如果將Utility模塊(JAR文件)拷貝到Web 模塊的 WEB-INF/lib 文件夾中,那么,Utility模塊將會被視為War模塊的一部分,如圖所示:
圖四、Utility模塊(JAR文件)在Web 模塊中
如果我們將WAR Classloader Policy設置為MODULE(默認設置),如圖五所示:
圖五、WAR Classloader Policy設置
-
那么WebSphere應用服務器會使用WAR Classloader來裝載WAR模塊的類文件。而EJB模塊始終是通過Application Classloader進行裝載,由于Application Classloader處于WAR classloader的上層,EJB模塊無法引用War模塊的類文件。這樣,當SampleSessionBean調用SampleUtility時,會拋出異常:
SystemOut O SampleServlet invokes SampleUtility
SystemOut O SampleUtility invokes SampleSessionBean
SystemOut O SampleSessionBean invokes SampleUtility
ExceptionUtil E CNTR0020E: 在 bean"BeanId(Sample#SampleEJB.jar#SampleSession, null)"上處理方法"callSampleUtility"時發生非應用程序異常。異常數據:
java.lang.NoClassDefFoundError: sample/SampleUtility
at sample.SampleSessionBean.callSampleUtility(SampleSessionBean.java:41)
-
如果我們將WAR Classloader Policy設置為APPLICATION,那么WebSphere應用服務器會使用Application Classloader來裝載WAR模塊和EJB模塊的類文件。由于兩個模塊使用了相同的Classloader進行裝載,所以兩個模塊間的類可以相互引用應用服務器輸出的結果如下:
SystemOut O SampleServlet invokes SampleUtility
SystemOut O SampleUtility invokes SampleSessionBean
SystemOut O SampleSessionBean invokes SampleUtility
SystemOut O The process has been finished
2、如果將Utility模塊拷貝到%was_root%\lib\ext , Websphere應用服務器會使用WebSphere Extensions Classloader加載Utility模塊中的類文件,由于WebSphere Extensions Classloader處于Application classloader和WAR classloader的上層,所以SampleServlet和SampleSessionBean可以引用到SampleUtility,但是SampleUtility引用不到SampleSessionBean,因此,當SampleUtility調用SampleSessionBean時會拋出異常。
上面講的配置方法比較適合將已經開發完的J2EE應用程序按照原有的包結構部署到WebSphere上,但有的項目可能會在開發完部分子系統的時候,就要將J2EE應用程序遷移到WebSphere上,然后,在WSAD中繼續開發未完成的子系統。這樣,把UTILITY模塊打包到WAR模塊的WEB-INF/lib 文件夾中,將使得開發公用類變得繁瑣。幸運的是,WSAD提供了一個方式使公用類的開發和調試方法變得簡單、清晰。
我們還用上面的實例進行演示。
首先,我們要為UTILITY模塊創建一個JAVA項目。然后將utility.jar中的SampleUtility.java導入到此JAVA項目(頁可以稱作實用程序項目)當中。創建項目的結果如下圖所示:
圖六、實用程序項目
然后,將此實用程序項目添加到應用程序部署描述符當中,如圖七所示:
1、 選擇"Sample"項目中的"應用程序部署描述符";
2、 選擇"模塊";
3、 在"項目實用程序JAR欄目中"點擊"添加";
4、 選擇"Utility"項目,點擊"完成"。
圖七、應用程序部署描述符
在所有依賴于此模塊的項目中,添加JAR模塊依賴項(主要包括EJB模塊和WEB模塊):
1、在EJB模塊和WEB模塊分別編輯MANIFEST.MF文件,雙擊MANIFEST.MF文件可打開可視化編輯器編輯此文件
圖八、MANIFEST.MF文件
2、 在EJB模塊的MANIFEST.MF文件中選擇Utility.jar
圖九、EJB模塊的MANIFEST.MF文件
3、 在WEB模塊的MANIFEST.MF文件中選擇Utility.jar
圖十、WEB模塊的MANIFEST.MF文件
接下來,我們可以啟動應用服務器,對SampleServlet進行測試,控制臺的顯示結果如下:
SystemOut O SampleServlet invokes SampleUtility
SystemOut O SampleUtility invokes SampleSessionBean
SystemOut O SampleSessionBean invokes SampleUtility
SystemOut O The process has been finished