理解Java類加載原理

本文轉自:http://www.moon-soft.com/doc/22670.htm

第一部分. 提示
我需要讀這篇文章嗎?
Java類加載器對Java系統的運行是至關重要的,但是卻常常被我們忽略。Java類加載器負載在運行時查找和加載類。自定義類加載器可以完全改變類的加載方式,以自己喜歡的方式來個性化你的Java虛擬機。本文簡要的介紹Java類加載器,然後通過一個構造自定義類加載器的例子來說明,這個類加載器在加載類前會自動編譯代碼。你將學到類加載器到底是幹什麼的,如何創建你自己的類加載器。只要你有一些基本的Java知識,知道如何創建、編譯、運行一個命令行Java程序以及一些Java類文件的基本概念,你就可以理解本文的內容了。讀完本文,你應該能夠:
* 擴張Java虛擬機的功能
* 創建一個自定義的類加載器
* 如何把自定義的類加載器整合到你的應用程序中
* 修改你的類加載器以兼容Java2
獲得幫助 
對本文有任何問題,可以聯繫作者Greg Travis,油箱:[email protected]
第二部分. 簡介
類加載器是什麼?
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 檢查是否可以從本地文件系統中記載。
* 如果參數resolve爲true,調用resolveClass來解析類對象。
* 如果還沒有找到類,拋出一個ClassNotFoundException異常。
* 否則,返回這個類。
現在我們對類加載器的應用知識有個較全面的瞭解,可以創建自定義類加載器了。在下一部分,我們將討論CCL。
第四部分. CompilingClassLoader
CCL給我們展示了類加載器的功能, CCL的目的是讓我們的代碼能夠自動編譯和更新。下面描述它是怎麼工作的:
* 當有一個類的請求時,先檢查磁盤的當前目錄和子目錄上是否存在這個類文件。
* 如果沒有類文件,但是卻有源代碼文件,調用Java編譯器編譯生成類文件。
* 如果類文件已經存在,檢查該類文件是否比源代碼文件陳舊。如果類文件比源代碼文件陳舊,調用Java編譯器重新生成類文件。
* 如果編譯失敗,或者由於其他原因導致無法從源文件生成類文件,拋出異常ClassNotFoundException。
* 如果還沒有獲得這個類,可能存在其他的類庫裏,調用findSystemClass看是否能找到。

* 如果沒有找到,拋出異常ClassNotFoundException。
* 否則,返回該類。
Java編譯是如何實現的?
在我們進一步討論前,我們需要先弄清楚Java的編譯過程。通常,Java編譯器不僅僅編譯指定的那些類。如果指定的那些類需要的話,它還會編譯其它的一些相關類。CCL會一個一個的編譯我們在應用程序中需要編譯的那些類。不過,一般來說,編譯器編譯完第一個類後,CCL將會發現其實其他需要的相關類已經被編譯了。爲什麼呢?Java編譯器使用我們差不多的規則:如果一個了類不存在或者源文件已經被更新,就會編譯這個類。Java編譯器基本上比CCL早了一步,大部分工作都被Java編譯器完成了。我們看起來就像是CCL在編譯這些類。在大多數情況下,你將發現它是在主函數類中調用編譯器,就僅僅這些而已--簡單的一個調用就夠了。 不過有一種特殊情況,這些類在第一次出現的時候不編譯。如果你根據類名加載一個類,使用方法Class.forName,Java編譯器並不知道是否需要這個類。在這種情況下,你發現CCL再次調用編譯器來編譯該類。第六部分的代碼說明了這個過程。
使用CompilationClassLoader
爲了使用CCL,我們不能直接運行我們的程序,必須以一種特殊的方式運行,就像這樣:
% java Foo arg1 arg2
我們這樣運行它:
% java CCLRun Foo arg1 arg2
CCLRun是一個特殊的存根程序,它來創建CompilingClassLoader 並且用它來加載我們的主函數類,這樣可以確保所有的整個程序都是由CompilingClassLoader加載的。CCLRun利用Java反射API來調用主函數類的主函數並且給這個函數傳遞參數。想了解更多,參考第六部分的源代碼。
運行示例
我們演示一下整個過程式怎麼工作的。
主程序是一個叫做Foo的類,它創建一個類Bar的實例。這個Bar實例又創建一個類Baz的實例,類Baz存在於包baz中,這是爲了演示CCL如何從子包中加載類。Bar還根據類名加載類Boo,這個也是CCL完成的。所有的類都加載了就可以運行了。利用第六章的源代碼來執行這個程序。編譯CCLRun和CompilingClassLoader。確保你沒有編譯其它的類(Foo, Bar, Baz, and Boo),否則CCL將不起作用,。
% java CCLRun Foo arg1 arg2
CCL: Compiling Foo.java...
foo! arg1 arg2
bar! arg1 arg2
baz! arg1 arg2
CCL: Compiling Boo.java...
Boo!
注意到爲了Foo.java第一次調用編譯器,同時也把Bar和baz.Baz一起編譯了俄。而類Boo直道需要加載的時候,CCL纔再次調用編譯器來編譯它。
第五部分.Java2中對類加載器的改進
概覽
在Java1.2和以後的版本中, 類加載器有了很大的改進。以前的代碼仍然可以工作, 但是新的系統讓我們的實現更容易。這種新模型就是代理委託模型,就是說如果這個類加載器找不到某個類,它會讓他的父類加載器來找。系統類加載器是所有類加載器的祖先, 系統類加載器通過默認的方式加載類--也就是從本地文件系統中加載。覆蓋loadClass方法一般都嘗試幾種方式來加載類,如果你寫了很多類加載器,你會發現你只是一次又一次在這個複雜的方法中作一些修改而已。Java1.2種loadClass的默認實現包含了尋找類的最普通的途徑,允許你覆蓋findClass方法,loadClass在適當的是否調用findClass方法。這樣做的好處是你不需要覆蓋loadClass,你只需要覆蓋findClass,這樣可以減少工作量。
新增方法: findClass
這個方法被loadClass的默認實現調用。findClass的目標是包含所有類加載器特定的代碼,而不需要重複這些代碼(比如在指定的方法失敗的時候調用系統類加載器)。
新增方法: getSystemClassLoader
不論你是否覆蓋方法findClass和loadClass, 方法getSystemClassLoader都可以直接訪問系統類加載器(而不是通過findSystemClass間接的訪問)。
新增方法: getParent
爲了把請求委託給父類加載器,通過這個方法可以獲得這個類加載器的父類加載器。當自定義類加載器中的特定方法無法找到類的時候你可能把請求委託給父類加載器。類加載器的父
類加載器包含創建這個類加載器的代碼。
第六部分. 源代碼
CompilingClassLoader.java
以下是文件CompilingClassLoader.java內容
view plaincopy to clipboardprint?
import java.io.*;  
/* 
CompilingClassLoader動態的編譯Java源文件。它檢查.class文件是否存在,.class文件是否比源文件陳舊。 
*/ 
public class CompilingClassLoader extends ClassLoader  
{  
// 指定一個文件名,從磁盤讀取整個文件內容,返回字節數組。  
private byte[] getBytes( String filename ) throws IOException {  
// 獲得文件大小。  
File file = new File( filename );  
long len = file.length();  
//創建一個數組剛好可以存放文件的內容。  
byte raw[] = new byte[(int)len];  
// 打開文件  
FileInputStream fin = new FileInputStream( file );  
// 讀取所有內容,如果沒法讀取,表示發生了一個錯誤。  
int r = fin.read( raw );  
if (r != len)  
throw new IOException( "Can't read all, "+r+" != "+len );  
// 別忘了關閉文件。  
fin.close();  
// 返回這個數組。  
return raw;  
}  
// 產生一個進程來編譯指定的Java源文件,制定文件參數.如果編譯成功返回true,否者,  
// 返回false。  
private boolean compile( String javaFile ) throws IOException {  
// 顯示當前進度  
System.out.println( "CCL: Compiling "+javaFile+"..." );  
// 啓動編譯器  
Process p = Runtime.getRuntime().exec( "javac "+javaFile );  
// 等待編譯結束  
try {  
p.waitFor();  
} catch( InterruptedException ie ) { System.out.println( ie ); }  
// 檢查返回碼,看編譯是否出錯。  
int ret = p.exitValue();  
// 返回編譯是否成功。  
return ret==0;  
}  
// 類加載器的核心代碼 -加載類在需要的時候自動編譯源文件。  
public Class loadClass( String name, boolean resolve )  
throws ClassNotFoundException {  
//我們的目的是獲得一個類對象。  
Class clas = null;  
// 首先,檢查是否已經出理過這個類。  
clas = findLoadedClass( name );  
//System.out.println( "findLoadedClass: "+clas );  
// 通過類名獲得路徑名 比如:java.lang.Object => java/lang/Object  
String fileStub = name.replace( '.', '/' );  
// 構建指向源文件和類文件的對象。  
String javaFilename = fileStub+".java";  
String classFilename = fileStub+".class";  
File javaFile = new File( javaFilename );  
File classFile = new File( classFilename );  
//System.out.println( "j "+javaFile.lastModified()+" c "  
//+classFile.lastModified() );  
// 首先,判斷是否需要編譯。如果源文件存在而類文件不存在,或者都存在,但是源文件  
// 較新,說明需要編譯。  
if (javaFile.exists() &&(!classFile.exists() ||  
javaFile.lastModified() > classFile.lastModified())) {  
try {  
// 編譯,如果編譯失敗,我們必須聲明失敗原因(僅僅使用陳舊的類是不夠的)。  
if (!compile( javaFilename ) || !classFile.exists()) {  
throw new ClassNotFoundException( "Compile failed: "+javaFilename );  
}  
} catch( IOException ie ) {  
// 可能編譯時出現IO錯誤。  
throw new ClassNotFoundException( ie.toString() );  
}  
}  
// 確保已經正確編譯或者不需要編譯,我們開始加載原始字節。  
try {  
// 讀取字節。  
byte raw[] = getBytes( classFilename );  
// 轉化爲類對象  
clas = defineClass( name, raw, 0, raw.length );  
} catch( IOException ie ) {  
// 這裏並不表示失敗,可能我們處理的類在本地類庫中,如java.lang.Object。  
}  
//System.out.println( "defineClass: "+clas );  
//可能在類庫中,以默認的方式加載。  
if (clas==null) {  
clas = findSystemClass( name );  
}  
//System.out.println( "findSystemClass: "+clas );  
// 如果參數resolve爲true,根據需要解釋類。  
if (resolve && clas != null)  
resolveClass( clas );  
// 如果還沒有獲得類,說明出錯了。  
if (clas == null)  
throw new ClassNotFoundException( name );  
// 否則,返回這個類對象。  
return clas;  
}  

import java.io.*;
/*
CompilingClassLoader動態的編譯Java源文件。它檢查.class文件是否存在,.class文件是否比源文件陳舊。
*/
public class CompilingClassLoader extends ClassLoader
{
// 指定一個文件名,從磁盤讀取整個文件內容,返回字節數組。
private byte[] getBytes( String filename ) throws IOException {
// 獲得文件大小。
File file = new File( filename );
long len = file.length();
//創建一個數組剛好可以存放文件的內容。
byte raw[] = new byte[(int)len];
// 打開文件
FileInputStream fin = new FileInputStream( file );
// 讀取所有內容,如果沒法讀取,表示發生了一個錯誤。
int r = fin.read( raw );
if (r != len)
throw new IOException( "Can't read all, "+r+" != "+len );
// 別忘了關閉文件。
fin.close();
// 返回這個數組。
return raw;
}
// 產生一個進程來編譯指定的Java源文件,制定文件參數.如果編譯成功返回true,否者,
// 返回false。
private boolean compile( String javaFile ) throws IOException {
// 顯示當前進度
System.out.println( "CCL: Compiling "+javaFile+"..." );
// 啓動編譯器
Process p = Runtime.getRuntime().exec( "javac "+javaFile );
// 等待編譯結束
try {
p.waitFor();
} catch( InterruptedException ie ) { System.out.println( ie ); }
// 檢查返回碼,看編譯是否出錯。
int ret = p.exitValue();
// 返回編譯是否成功。
return ret==0;
}
// 類加載器的核心代碼 -加載類在需要的時候自動編譯源文件。
public Class loadClass( String name, boolean resolve )
throws ClassNotFoundException {
//我們的目的是獲得一個類對象。
Class clas = null;
// 首先,檢查是否已經出理過這個類。
clas = findLoadedClass( name );
//System.out.println( "findLoadedClass: "+clas );
// 通過類名獲得路徑名 比如:java.lang.Object => java/lang/Object
String fileStub = name.replace( '.', '/' );
// 構建指向源文件和類文件的對象。
String javaFilename = fileStub+".java";
String classFilename = fileStub+".class";
File javaFile = new File( javaFilename );
File classFile = new File( classFilename );
//System.out.println( "j "+javaFile.lastModified()+" c "
//+classFile.lastModified() );
// 首先,判斷是否需要編譯。如果源文件存在而類文件不存在,或者都存在,但是源文件
// 較新,說明需要編譯。
if (javaFile.exists() &&(!classFile.exists() ||
javaFile.lastModified() > classFile.lastModified())) {
try {
// 編譯,如果編譯失敗,我們必須聲明失敗原因(僅僅使用陳舊的類是不夠的)。
if (!compile( javaFilename ) || !classFile.exists()) {
throw new ClassNotFoundException( "Compile failed: "+javaFilename );
}
} catch( IOException ie ) {
// 可能編譯時出現IO錯誤。
throw new ClassNotFoundException( ie.toString() );
}
}
// 確保已經正確編譯或者不需要編譯,我們開始加載原始字節。
try {
// 讀取字節。
byte raw[] = getBytes( classFilename );
// 轉化爲類對象
clas = defineClass( name, raw, 0, raw.length );
} catch( IOException ie ) {
// 這裏並不表示失敗,可能我們處理的類在本地類庫中,如java.lang.Object。
}
//System.out.println( "defineClass: "+clas );
//可能在類庫中,以默認的方式加載。
if (clas==null) {
clas = findSystemClass( name );
}
//System.out.println( "findSystemClass: "+clas );
// 如果參數resolve爲true,根據需要解釋類。
if (resolve && clas != null)
resolveClass( clas );
// 如果還沒有獲得類,說明出錯了。
if (clas == null)
throw new ClassNotFoundException( name );
// 否則,返回這個類對象。
return clas;
}
}
 

CCRun.java
一下是CCRun.java文件
view plaincopy to clipboardprint?
import java.lang.reflect.*;  
/* 
CCLRun通過CompilingClassLoader加載類來運行程序。 
*/ 
public class CCLRun  
{  
static public void main( String args[] ) throws Exception {  
// 第一個參數指定用戶要運行的主函數類。  
String progClass = args[0];  
// 接下來的參數是傳給這個主函數類的參數。  
String progArgs[] = new String[args.length-1];  
System.arraycopy( args, 1, progArgs, 0, progArgs.length );  
// 創建CompilingClassLoader  
CompilingClassLoader ccl = new CompilingClassLoader();  
// 通過CCL加載主函數類。  
Class clas = ccl.loadClass( progClass );  
// 利用反射調用它的主函數和傳遞參數。  
// 產生一個代表主函數的參數類型的類對象。  
Class mainArgType[] = { (new String[0]).getClass() };  
// 在類中找到標準的主函數。  
Method main = clas.getMethod( "main", mainArgType );  
// 創建參數列表 -在這裏,是一個字符串數組。  
Object argsArray[] = { progArgs };  
// 調用主函數。  
main.invoke( null, argsArray );  
}  

import java.lang.reflect.*;
/*
CCLRun通過CompilingClassLoader加載類來運行程序。
*/
public class CCLRun
{
static public void main( String args[] ) throws Exception {
// 第一個參數指定用戶要運行的主函數類。
String progClass = args[0];
// 接下來的參數是傳給這個主函數類的參數。
String progArgs[] = new String[args.length-1];
System.arraycopy( args, 1, progArgs, 0, progArgs.length );
// 創建CompilingClassLoader
CompilingClassLoader ccl = new CompilingClassLoader();
// 通過CCL加載主函數類。
Class clas = ccl.loadClass( progClass );
// 利用反射調用它的主函數和傳遞參數。
// 產生一個代表主函數的參數類型的類對象。
Class mainArgType[] = { (new String[0]).getClass() };
// 在類中找到標準的主函數。
Method main = clas.getMethod( "main", mainArgType );
// 創建參數列表 -在這裏,是一個字符串數組。
Object argsArray[] = { progArgs };
// 調用主函數。
main.invoke( null, argsArray );
}
}

Foo.java
以下是文件Foo.java內容
view plaincopy to clipboardprint?
public class Foo  
{  
static public void main( String args[] ) throws Exception {  
System.out.println( "foo! "+args[0]+" "+args[1] );  
new Bar( args[0], args[1] );  
}  

public class Foo
{
static public void main( String args[] ) throws Exception {
System.out.println( "foo! "+args[0]+" "+args[1] );
new Bar( args[0], args[1] );
}
}

Bar.java
以下是文件Bar.java內容
view plaincopy to clipboardprint?
import baz.*;  
public class Bar  
{  
public Bar( String a, String b ) {  
System.out.println( "bar! "+a+" "+b );  
new Baz( a, b );  
try {  
Class booClass = Class.forName( "Boo" );  
Object boo = booClass.newInstance();  
} catch( Exception e ) {  
e.printStackTrace();  
}  
}  

import baz.*;
public class Bar
{
public Bar( String a, String b ) {
System.out.println( "bar! "+a+" "+b );
new Baz( a, b );
try {
Class booClass = Class.forName( "Boo" );
Object boo = booClass.newInstance();
} catch( Exception e ) {
e.printStackTrace();
}
}
}
baz/Baz.java
以下是文件baz/Baz.java內容
view plaincopy to clipboardprint?
package baz;  
public class Baz  
{  
public Baz( String a, String b ) {  
System.out.println( "baz! "+a+" "+b );  
}  

package baz;
public class Baz
{
public Baz( String a, String b ) {
System.out.println( "baz! "+a+" "+b );
}
}
Boo.java
以下是文件Boo.java內容
view plaincopy to clipboardprint?
public class Boo  
{  
public Boo() {  
System.out.println( "Boo!" );  
}  

public class Boo
{
public Boo() {
System.out.println( "Boo!" );
}
}
 

第七部分. 總結
總結
通過本文你是否意識到,創建自定義類加載器可以讓你深入到Java虛擬機的內部。你可以從任何資源加載類文件,或者動態的生成它,這樣你就可以通過擴展這些功能做很多你感興趣的事,還能完成一些強大的功能。
關於ClassLoader的其它話題
就像本文開頭說的,自定義類加載器在Java嵌入式瀏覽器和小應用程序瀏覽器中起着重要的作用。下面給出類加載器的其它功能。
* 安全: 自定義的類加載器可以在把這個類交給Java虛擬機之前檢查它是否有正確的數字簽名。你也可以自己創建一個"沙箱"來阻止對某些方法的調用,這是通過檢查源代碼,阻止該類對沙箱之外的操作來實現的。
* 加密:通過自定義類加載器可以動態的解碼,所有你的類文件就無法通過反編譯被查看到代碼。用戶需要密碼才能運行程序,這個密碼用來對代碼解密。
* 存檔:你是否需要將你的代碼以某種格式或者壓縮形式發佈嗎?自定義ClassLoader可以從你想要的任何資源中生成字節碼文件。
* 自提取程序:可以把整個應用程序編譯到一個可執行的類文件中,這個文件包括壓縮過或者加密過的數據,有了內部類加載器,當程序運行的時候,他把自己解包到內存-不需要事前安裝。
* 動態生成:可以動態的生成那些被引用的類-整個程序需要用的類都可以動態的生成然後交給Java虛擬機。

發佈了35 篇原創文章 · 獲贊 0 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章