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類加載器介紹)還是覆蓋它。
類加載模式有兩個可能的選項:
l 父類優先
l 應用程序優先
在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:\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_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:\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_FIRST
從上述跟蹤信息中,可以得到:
1.類加載器的類型是:
com.ibm.ws.classloader.CompoundClassLoader.
2.查找類的順序是:
ClassloaderExampleWeb.war\WEB-INF\classes
ClassloaderExampleWeb.war\WEB-INF\lib\VersionCheckerV1.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 類。