ClassLoader轉

 

關於ClassLoader [轉]
2009-04-22 00:46

Java本身是一種設計的非常簡單,非常精巧的語言,所以Java背後的原理也很簡單,歸結起來就是兩點:

1、JVM的內存管理

理解了這一點,所有和對象相關的問題統統都能解決

2、JVM Class Loader

理解了這一點,所有和Java相關的配置問題,包括各種App Server的配置,應用的發佈問題統統都能解決

App Class Loader
|----- EJB Class Loader
|----- Web App Class Loader

如果在App Class Loader級別配置,是全局可見的。如果打包在EJB裏面,那麼就不會影響到Web Application,反之亦然,如果你在WEB-INF下面放置Hibernate,也不會影響到EJB。放在EJB Class Loader或者放在Web App Class Loader級別主要就是在局部範圍內有效,不影響到其它的應用。

試想,如果在一個Weblogic上面配置多個虛擬域,你使用www.bruce.com域名,開發你的網站,我使用www.fankai.com開發我的網站,那麼當然不希望我們的Hibernate相互干擾,所以就可以放在 EJB Class Loader級別來配置Hibernate。

進一步闡述一下EJB Class Loader的問題:

先再次強調一下,Hibernate和EJB,和App Server不存在兼容性問題,他們本來就是不相關的東西,就好像JDBC,相信沒有人會認爲JDBC和EJB不兼容吧,Hibernate也是一樣,它只和JDBC驅動,和數據庫有兼容性問題,而和EJB,和App Server完全是不搭界的兩回事。凡是認爲Hibernate和EJB不兼容的人,其實是都是因爲對EJB學習的不到家,把責任推到Hibernate身上了。

我前面提到過Class Loader的層次,這裏不重複了,總之我們先來看看Class Loader的作用範圍:

((Boot Strap)) Class Loader:
load JRE/lib/rt.jar, sunrsasign.jar, charsets.jar, jce.jar, jsse.jar, plugin.jar
Ext Class Loader:
load JRE/lib/ext目錄下的庫文件, load JRE/classes目錄下的類
App Class Loader:
load CLASSPATH變量指定路徑下的類

以上的load路徑都是寫死在JVM的C++源代碼裏面的,不能改變,詳細請見王森的《Java深度歷險》

在一個特定的App Server上,Class Loader會繼續向下繼承,繼承的層次會根據不同的App Server有所不同,但是肯定不會變的就是:

EJB Class Loader:

繼承自App Class Loader,繼承層次根據App Server有所不同,一個EJB Class Loader它的load Class的範圍僅限於JAR或者EAR範圍之內。

Web App Class Loader:

繼承自App Class Loader,繼承層次根據App Server有所不同,一個Web App Class Loader:它的load Class的範圍在 WEB-INF/lib下的庫文件和WEB-INF/classes目錄下的class文件。

Web App Class Loader很好理解,大家畢竟用的很多,App Server上的一個Web Application會創建一個Web App Class Loader的實例去負責load class,所以如果你想讓Hibernate只在這個Web Application內生效,把它放到WEB-INF/lib下去就好了。

如果你把Hibernate放到了CLASSPATH變量指定的路徑下,而你在WEB-INF/lib也放了一份,那麼Web App Class Loader由於load範圍所限,它會首先找到WEB-INF/lib下的那份Hibernate,按照它的配置來初始化Hibernate。

如果你把Hibernate放到了CLASSPATH變量指定的路徑下,但你在WEB-INF/lib什麼都沒有放,那麼Web App Class Loader由於load範圍所限,它根本什麼都找不到,於是它把load Hibernate的責任交給上一級的Class Loader,這樣直到App Class Loader,它找到了Hibernate,按照它的配置來初始化Hibernate。

EJB Class Loader稍微複雜一點,不那麼容易理解。App Server會針對每一個EJB包文件創建一個EJB Class Loader的實例,例如:

((Hello Robbin)).jar
((Hello Bruce)).jar

當你把這兩個jar發佈到App Server上以後,會創建兩個EJB Class Loader的實例,分別去load這兩個EJB包,比如說:

CLEJB_Robbin是load ((Hello Robbin)).jar的
CLEJB_Bruce是load ((Hello Bruce)).jar的

那麼CLEJB_Robbin的load範圍就僅僅限於HelloRobbin.jar之內,它load不到HelloRobbin.jar之外的任何文件,當然它也load不到HelloBruce.jar。

說到這裏,我相信大家應該已經明白爲什麼EJB規範不允許EJB有IO操作了吧?因爲EJB Class Loader根本找不到jar包之外的文件!!!

如果現在你想實現HelloRobbin.jar和HelloBruce.jar的互相調用,那麼該怎麼辦?他們使用了不同的EJB Class Loader,相互之間是找不到對方的。解決辦法就是使用EAR。

現在假設HelloRobbin.jar和HelloBruce.jar都使用了Hibernate,看看該怎麼打包和發佈:

HelloEJB.ear

|------ ((Hello Robbin)).jar
|------ ((Hello Bruce)).jar
|------ Hibernate2.jar
|------ pojo.jar (定義所有的持久對象和hbm文件的jar包)
|------ cglib-asm.jar
|------ commons-beanutils.jar
|------ commons-collections.jar
|------ commons-lang.jar
|------ commons-logging.jar
|------ dom4j.jar
|------ odmg.jar
|------ log4j.jar
|------ jcs.jar
|------ Hibernate.properties
|------ log4j.properties
|------ cache.ccf
|------ META-INF/application.xml (J2EE規範的要求,定義EAR包裏面包括了哪幾個EJB)

除此之外,按照EJB規範要求,HelloRobbin.jar和HelloBruce.jar還必須指出調用jar包之外的類庫的名稱,這需要在jar包的manifest文件中定義:

((Hello Robbin)).jar
|------ META-INF/MANIFEST.MF

MANIFEST.MF中必須包括如下一行:

Class-Path: log4j.jar hibernate2.jar cglib-asm.jar commons-beanutils.jar commons-collections.jar commons-lang.jar
commons-logging.jar dom4j.jar jcs.jar odmg.jar jcs.jar pojo.jar

這樣就OK了,當把HelloEJB.ear發佈到App Server上以後,App Server創建一個EJB Class Loader實例load EAR包裏面的EJB,再根據EJB的jar包裏面的MANIFEST.MF指出的Class-Path去尋找相應的jar包之外的類庫。

所以一個EAR包有點類似一個Web Application,EJB Class Loader的load範圍也就是EAR範圍之內,它load不到EAR之外的文件。除非把Hibernate定義到CLASSPATH指定的路徑下,在這種情況下,EJB Class Loader找不到Hibernate,只能交給上一級的Class Loader,最後由App Class Loader找到Hibernate,進行初始化。

由於EAR這樣load Class規則,假設Robbin和Bruce都在同一個Weblogic上運行自己的網站,而我們都不希望自己的程序裏面的Hibernate配置被對方的搞亂掉,那麼我們就可以這樣來做:

Robbin's Website:

Robbin.ear

|-------- robbin.war (把Web Application打包)
|-------- robbin.jar (把開發的EJB打包)
|-------- Hibernate2.jar
..........................
|-------- META-INF/application.xml

Bruce's Website:

Bruce.ear
|-------- bruce.war (把Web Application打包)
|-------- bruce.jar (把開發的EJB打包)
|-------- Hibernate2.jar
..........................
|-------- META-INF/application.xml

這樣在同一個App Server上運行,就可以互相不干擾。

#################################################
richardluo 發表自dev2dev@bea

瞭解ClassLoader
1, 什麼是 ClassLoader?
Java 程序並不是一個可執行文件,是需要的時候,才把裝載到 JVM中。ClassLoader 做的工作就是 JVM 中將類裝入內存。 而且,Java ClassLoader 就是用 Java 語言編寫的。這意味着您可以創建自己的 ClassLoader
ClassLoader 的基本目標是對類的請求提供服務。當 JVM 需要使用類時,它根據名稱向 ClassLoader 請求這個類,然後 ClassLoader 試圖返回一個表示這個類的 Class 對象。通過覆蓋對應於這個過程不同階段的方法,可以創建定製的 ClassLoader。
2, 一些重要的方法
A) 方法 loadClass
ClassLoader.loadClass() 是 ClassLoader 的入口點。該方法的定義如下:
Class loadClass( String name, boolean resolve ;
name JVM 需要的類的名稱,如 Foo 或 java.lang.Object。
resolve 參數告訴方法是否需要解析類。在準備執行類之前,應考慮類解析。並不總是需要解析。如果 JVM 只需要知道該類是否存在或找出該類的超類,那麼就不需要解析。

B) 方法 defineClass
defineClass 方法是 ClassLoader 的主要訣竅。該方法接受由原始字節組成的數組並把它轉換成 Class 對象。原始數組包含如從文件系統或網絡裝入的數據。defineClass 管理 JVM 的許多複雜、神祕和倚賴於實現的方面 -- 它把字節碼分析成運行時數據結構、校驗有效性等等。不必擔心,您無需親自編寫它。事實上,即使您想要這麼做也不能覆蓋它,因爲它已被標記成final的。

C) 方法 findSystemClass
findSystemClass 方法從本地文件系統裝入文件。它在本地文件系統中尋找類文件,如果存在,就使用 defineClass 將原始字節轉換成 Class 對象,以將該文件轉換成類。當運行 Java 應用程序時,這是 JVM 正常裝入類的缺省機制。(Java 2 中 ClassLoader 的變動提供了關於 Java 版本 1.2 這個過程變動的詳細信息。) 對於定製的 ClassLoader,只有在嘗試其它方法裝入類之後,再使用 findSystemClass。原因很簡單:ClassLoader 是負責執行裝入類的特殊步驟,不是負責所有類。例如,即使 ClassLoader 從遠程的 Web 站點裝入了某些類,仍然需要在本地機器上裝入大量的基本 Java 庫。而這些類不是我們所關心的,所以要 JVM 以缺省方式裝入它們:從本地文件系統。這就是 findSystemClass 的用途。

D) 方法 resolveClass
正如前面所提到的,可以不完全地(不帶解析)裝入類,也可以完全地(帶解析)裝入類。當編寫我們自己的 loadClass 時,可以調用 resolveClass,這取決於 loadClass 的 resolve 參數的值。

E) 方法 findLoadedClass
findLoadedClass 充當一個緩存:當請求 loadClass 裝入類時,它調用該方法來查看 ClassLoader 是否已裝入這個類,這樣可以避免重新裝入已存在類所造成的麻煩。應首先調用該方法。

3, 怎麼組裝這些方法
1) 調用 findLoadedClass 來查看是否存在已裝入的類。
2) 如果沒有,那麼採用那種特殊的神奇方式來獲取原始字節。
3) 如果已有原始字節,調用 defineClass 將它們轉換成 Class 對象。
4) 如果沒有原始字節,然後調用 findSystemClass 查看是否從本地文件系統獲取類。
5) 如果 resolve 參數是 true,那麼調用 resolveClass 解析 Class 對象。
6) 如果還沒有類,返回 ClassNotFoundException。

4,Java 2 中 ClassLoader 的變動
1)loadClass 的缺省實現
定製編寫的 loadClass 方法一般嘗試幾種方式來裝入所請求的類,如果您編寫許多類,會發現一次次地在相同的、很複雜的方法上編寫變量。在 Java 1.2 中 loadClass 的實現嵌入了大多數查找類的一般方法,並使您通過覆蓋 findClass 方法來定製它,在適當的時候 findClass 會調用 loadClass。這種方式的好處是您可能不一定要覆蓋 loadClass;只要覆蓋 findClass 就行了,這減少了工作量。

2)新方法:findClass
loadClass 的缺省實現調用這個新方法。findClass 的用途包含您的 ClassLoader 的所有特殊代碼,而無需要複製其它代碼(例如,當專門的方法失敗時,調用系統 ClassLoader)。

3) 新方法:getSystemClassLoader
如果覆蓋 findClass 或 loadClass,getSystemClassLoader 使您能以實際 ClassLoader 對象來訪問系統 ClassLoader(而不是固定的從 findSystemClass 調用它)。

4) 新方法:getParent
爲了將類請求委託給父代 ClassLoader,這個新方法允許 ClassLoader 獲取它的父代 ClassLoader。當使用特殊方法,定製的 ClassLoader 不能找到類時,可以使用這種方法。
父代 ClassLoader 被定義成創建該 ClassLoader 所包含代碼的對象的 ClassLoader。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章