<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    (轉)理解Java類加載原理(翻譯)

    Posted on 2007-04-08 10:11 久城 閱讀(266) 評論(0)  編輯  收藏 所屬分類: 轉載收藏
    第一部分. 提示
    我需要讀這篇文章嗎?
    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如何執行從遠程網站下載的代碼。執行從遠程站點通過HTT
    P連接傳送來的字節碼看起來有點不可思議。之所以能夠工作,因為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虛擬機需要一個類的時候,就把一個類
    名給類加載器,然后類加載器試圖返回一個對應的類實例。可以通過在不同的階段覆蓋相應
    的方法來創建自定義的類加載器。接下來我們將了解到類加載器的一些主要方法。你會明白
    這些方法是干什么的,他們在加載類文件的時候是如何工作的。你還將知道創建自定義類加
    載器的時候需要寫哪些代碼。在下一部分,你將利用這些知識和我們自定義的CompilingCl
    assLoader一起工作。
    方法 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編譯器重新生成類文件。
    * 如果編譯失敗,或者由于其他原因導致無法從源文件生成類文件,拋出異常ClassNotFou
    ndException。
    * 如果還沒有獲得這個類,可能存在其他的類庫里,調用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利用Ja
    va反射API來調用主函數類的主函數并且給這個函數傳遞參數。想了解更多,參考第六部分
    的源代碼。
    運行示例
    我們演示一下整個過程式怎么工作的。
    主程序是一個叫做Foo的類,它創建一個類Bar的實例。這個Bar實例又創建一個類Baz的實例
    ,類Baz存在于包baz中,這是為了演示CCL如何從子包中加載類。Bar還根據類名加載類Boo
    ,這個也是CCL完成的。所有的類都加載了就可以運行了。利用第六章的源代碼來執行這個
    程序。編譯CCLRun和CompilingClassLoader。確保你沒有編譯其它的類(Foo, Bar, Baz, a
    nd 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內容
    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文件
    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內容
    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內容
    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內容
    package baz;
    public class Baz
    {
    public Baz( String a, String b ) {
    System.out.println( "baz! "+a+" "+b );
    }
    }
    Boo.java
    以下是文件Boo.java內容
    public class Boo
    {
    public Boo() {
    System.out.println( "Boo!" );
    }
    }
    第七部分. 總結
    總結
    通過本文你是否意識到,創建自定義類加載器可以讓你深入到Java虛擬機的內部。你可以從
    任何資源加載類文件,或者動態的生成它,這樣你就可以通過擴展這些功能做很多你感興趣
    的事,還能完成一些強大的功能。
    關于ClassLoader的其它話題
    就像本文開頭說的,自定義類加載器在Java嵌入式瀏覽器和小應用程序瀏覽器中起著重要的
    作用。下面給出類加載器的其它功能。
    * 安全: 自定義的類加載器可以在把這個類交給Java虛擬機之前檢查它是否有正確的數字
    簽名。你也可以自己創建一個"沙箱"來阻止對某些方法的調用,這是通過檢查源代碼,阻止
    該類對沙箱之外的操作來實現的。
    * 加密:通過自定義類加載器可以動態的解碼,所有你的類文件就無法通過反編譯被查看到
    代碼。用戶需要密碼才能運行程序,這個密碼用來對代碼解密。
    * 存檔:你是否需要將你的代碼以某種格式或者壓縮形式發布嗎?自定義ClassLoader可以
    從你想要的任何資源中生成字節碼文件。
    * 自提取程序:可以把整個應用程序編譯到一個可執行的類文件中,這個文件包括壓縮過或
    者加密過的數據,有了內部類加載器,當程序運行的時候,他把自己解包到內存-不需要事
    前安裝。
    * 動態生成:可以動態的生成那些被引用的類-整個程序需要用的類都可以動態的生成然后
    交給Java虛擬機。


    Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=94763



    歡迎來訪!^.^!
    本BLOG僅用于個人學習交流!
    目的在于記錄個人成長.
    所有文字均屬于個人理解.
    如有錯誤,望多多指教!不勝感激!

    Copyright © 久城

    主站蜘蛛池模板: 亚洲精品乱码久久久久久久久久久久| 亚洲AV成人片色在线观看高潮| 国产精品久久永久免费| 黑人精品videos亚洲人| 国产免费阿v精品视频网址| 亚洲精品午夜无码专区| 国产无遮挡色视频免费观看性色| 久久免费高清视频| 亚洲免费观看视频| 好湿好大好紧好爽免费视频| 在线观看亚洲天天一三视| 99热在线日韩精品免费| 亚洲AV无码成人专区片在线观看| 国产 亚洲 中文在线 字幕| 久久不见久久见免费影院| 亚洲国产精品成人精品软件| 在线观看AV片永久免费| 亚洲av日韩综合一区二区三区| 久久午夜免费鲁丝片| 亚洲免费一级视频| 免费黄色毛片视频| 七次郎成人免费线路视频| 在线观看亚洲av每日更新| 91人成网站色www免费下载| 亚洲偷偷自拍高清| 亚洲第一页日韩专区| 免费无码又爽又刺激一高潮| 亚洲福利视频网址| 国产精品美女自在线观看免费| 亚洲午夜电影一区二区三区| 久久综合AV免费观看| 青青草97国产精品免费观看 | 亚洲91av视频| 成全影视免费观看大全二| 免费的黄色的网站| 亚洲综合无码一区二区| 蜜桃精品免费久久久久影院| 日本三级在线观看免费| 亚洲av永久中文无码精品| 亚洲精品国产精品乱码不卡√| 无码毛片一区二区三区视频免费播放|