理解Java類加載原理 – JAVA編程語言程序開發技術文章

 第一部分. 提示
我需要讀這篇文章嗎?
Java類加載器對Java系統的運行是至關重要的,但是卻常常被我們忽略。Java類加載器負載在運行時查找和加載類。自定義類加載器可以完全改變類的加載方式,以自己喜歡的方式來個性化你的Java虛擬機。本文簡要的介紹Java類加載器,然後通過一個構造自定義類加載器的例子來說明,這個類加載器在加載類前會自動編譯代碼。你將學到類加載器到底是幹什麼的,如何創建你自己的類加載器。隻要你有一些基本的Java知識,知道如何創建、編譯、運行一個命令行Java程序以及一些Java類文件的基本概念,你就可以理解本文的內容瞭。讀完本文,你應該能夠:
* 擴張Java虛擬機的功能
* 創建一個自定義的類加載器
* 如何把自定義的類加載器整合到你的應用程序中
* 修改你的類加載器以兼容Java2
獲得幫助 
對本文有任何問題,可以聯系作者Greg Travis,油箱:mito@panix.com 。
第二部分. 簡介
類加載器是什麼?
Java和其他語言不同的是,Java是運行於Java虛擬機(JVM)。這就意味著編譯後的代碼是以一種和平臺無關的格式保存的,而不是某種特定的機器上運行的格式。這種格式和傳統的可執行代碼格式有很多重要的區別。具體來說,不同於C或者C++程序,Java程序不是一個獨立的可執行文件,而是由很多分開的類文件組成,每個類文件對應一個Java類。 另外,這些類文件並不是馬上加載到內存,而是當程序需要的時候才加載。 類加載器就是Java虛擬機中用來把類加載到內存的工具。而且,Java類加載器也是用Java實現的。這樣你就不需要對Java虛擬機有深入的理解就可以很容易創建自己的類加載器瞭。
為什麼要創建類加載器?
既然Java虛擬金已經有瞭類加載器,我們還要自己創建其他的呢?問得好。默認的類加載器隻知道如何從本地系統加載類。當你的程序完全在本機編譯的話,默認的類加載器一般都工作的很好。但是Java中最激動人心的地方之一就是很容易的從網絡上而不隻是本地加載類。
舉個例子,瀏覽器可以通過自定義的類加載器加載類。 還有很多加載類的方式。除瞭簡單的從本地或者網絡外,你還可以通過自定義Java中最激動人心的地方之一:
* 執行非信任代碼前自動驗證數字簽名
* 根據用戶提供的密碼解密代碼
* 根據用戶的需要動態的創建類
你關心的任何東西都能方便的以字節碼的形式集成到你的應用中
自定義類加載器的例子
如果你已經使用過JDK(Java軟件開發包)中的appletviewer(小應用程序瀏覽器)或者其他Java嵌入式瀏覽器,你就已經使用瞭自定義類加載器瞭。Sun剛剛發佈Java語言的時候,最令人興奮的一件事就是觀看Java如何執行從遠程網站下載的代碼。執行從遠程站點通過HTTP連接傳送來的字節碼看起來有點不可思議。之所以能夠工作,因為Java有安裝自定義類加載器的能力。小應用程序瀏覽器包含瞭一個類加載器,這個類加載器不從本地找Java類,而是訪問遠程服務器,通過HTTP加載原始字節碼文件,然後在Java虛擬機中轉化為Java類。當然類加載器還做瞭其他的很多事情:他們阻止不安全的Java類,而且保持不同頁面上的不同小程序不會互相幹擾。Luke Gorrie寫的一個包Echidna是一個開放的Java軟件包,他允許在一個Java虛擬機中安全的運行多個Java應用程序。它通過使用自定義類加載器給每個應用程序一份類文件的拷貝來阻止應用程序之間的幹擾。
我們的類加載器例子
我們知道瞭類加載器是如何工作的,也知道如何定義自己的類加載器瞭,接下來我們創建一個名字為CompilingClassLoader (CCL)的自定義類加載器。CCL為我們做編譯工作,我們就不用自己手動編譯瞭。 這基本上相當於有一個”make”程序構建到我們的運行環境。註意:我們進行下一步之前,有必要搞清楚一些相關的概念。系統在JDK版本1.2(也就是我們說的Java 2平臺)得到很到改進。本文是在JDK1.0和1.1的版本下寫的,但是所有的東西都能在後來的版本工作。ClassLoader也在Java2種有所改進,第五部分有詳細介紹。
第三部分.ClassLoader的結構
總攬
類加載器的基本目的是服務於對Java類的請求。Java虛擬機需要一個類的時候,就把一個類名給類加載器,然後類加載器試圖返回一個對應的類實例。可以通過在不同的階段覆蓋相應的方法來創建自定義的類加載器。接下來我們將瞭解到類加載器的一些主要方法。你會明白這些方法是幹什麼的,他們在加載類文件的時候是如何工作的。你還將知道創建自定義類加載器的時候需要寫哪些代碼。在下一部分,你將利用這些知識和我們自定義的CompilingClassLoader一起工作。
方法 loadClass
ClassLoader.loadClass() 是ClassLoader的入口點。方法簽名如下:
Class loadClass( String name, boolean resolve);
參數name指定Java虛擬機需要的類的全名(含包名),比如Foo或者java.lang.Object。
參數 resolve指定該類是否需要解析
你可以把類的解析理解為完全為運行做好準備。解析一般都不需要。如果Java虛擬機隻想知道這個類是否存在或者想知道它的父類的話,解析就完全沒有必要瞭。 在Java1.1和它以前的版本,如果要自定義類加載器,loadClass方法是唯一需要在子類中覆蓋的方法.
(ClassLoader在Java1.2中有所改變,提供瞭方法findClass())。
方法 defineClass
defineClass 是ClassLoader中一個很神秘的方法。這個方法通過一個字節數組來構建類實例。這個包含數據的原始字節數組可能來自文件系統,也可能是來自網絡。defineClass 表明瞭Java虛擬機的復雜性,神秘性和平臺依賴性-它通過解釋字節碼把它轉化為運行時數據結構,檢查有效性等等。但是不用擔心,這些都不用你去實現。其實,你根本不能覆蓋它,因為該方法被關鍵字final修飾。
方法 findSystemClass
findSystemClass方法從本地系統加載文件。它在本地系統尋找類文件,如果找到瞭,調用defineClass把原始字節數組轉化成類對象。這是運行Java應用時Java虛擬機加載類的默認機制。對於自定義類加載器,隻有在我們無法加載之後才需要用findSystemClass。 原因很簡單: 我們的類加載器負責執行類加載中的某些特定的步驟,但並不是對所有的類。比如,即使我們的類加載器從遠程站點加載瞭某些類,仍然有很多基本的類要從本地系統加載。
這些類不是我們關心的,所以我們讓Java虛擬機以默認的方式加載他們:從本地系統。這就是findSystemClass做的事情。整個過程大致如下:
* Java虛擬機請求我們自定義的類加載器加載類。
* 我們檢查遠程站點是否有這個需要加載的類。
* 如果有,我們獲取這個類。
* 如果沒有,我們認為這個是類在基本類庫中,調用findSystemClass從文件系統中加載。


在大多數自定義類加載器中,你應該先調用findSystemClass來節省從遠程查找的時間。實際上,正如我們將在下一部分看到的,隻有當我們確定我們已經自動編譯完我們的代碼後才允許Java虛擬機從本地文件系統加載類。
方法resolveClass
正如上面說的,類記載可以分為部分加載(不解析)和完全加載(包括解析)。我們創建自定義類加載器的時候,可能要調用resolveClass。
方法 findLoadedClass
findLoadedClass實現一個緩存:當要求loadClass來加載一個類的時候,可以先調用這個方法看看這個類是否已經被加載,防止重新加載一個已經被加載的類。這個方法必須先被調用,我們看一下這些方法是如何組織在一起的。
我們的例子實現loadClass執行以下的步驟。(我們不指定通過某種具體的技術獲得類文件,-它可能從網絡,從壓縮包或者動態編譯的。無論如何,我們獲得的是原始字節碼文件)
* 調用findLoadedClass檢查這個類是否已經加載。
* 如果沒有加載,我們通過某種方式獲得原始字節數組。
* 假如已經獲得該數組,調用defineClass把它轉化成類對象。
* 如果無法獲得該原始字節數組,調用findSystemClass 檢查是否可以從本地文件系統

發佈留言