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

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

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

    【永恒的瞬間】
    ?Give me hapy ?

    Java的類裝載器是Java動態性的核心,本文將向大家簡要介紹Java的類裝載器,及相關的parent delegation模型,命名空間,運行時包等概念,同時討論一些在學習中容易混淆的問題。

    類裝載器的功能及分類

    顧名思義,類裝載器是用來把類(class)裝載進JVM的。JVM規范定義了兩種類型的類裝載器:啟動內裝載器(bootstrap)和用戶自定義裝載器(user-defined class loader)。

    bootstrap是JVM自帶的類裝載器,用來裝載核心類庫,如java.lang.*等。由例1可以看出,java.lang.Object是由bootstrap裝載的。

    Java提供了抽象類ClassLoader,所有用戶自定義類裝載器都實例化自ClassLoader的子類。 System Class Loader是一個特殊的用戶自定義類裝載器,由JVM的實現者提供,在編程者不特別指定裝載器的情況下默認裝載用戶類。系統類裝載器可以通過ClassLoader.getSystemClassLoader() 方法得到。

    例1,測試你所使用的JVM的ClassLoader

    /*LoaderSample1.java*/
    public class LoaderSample1 {
    ??? public static void main(String[] args) {
    ??????? Class c;
    ??????? ClassLoader cl;
    ??????? cl = ClassLoader.getSystemClassLoader();
    ??????? System.out.println(cl);
    ??????? while (cl != null) {
    ??????????? cl = cl.getParent();
    ??????????? System.out.println(cl);
    ??????? }
    ??????? try {
    ??????????? c = Class.forName("java.lang.Object");
    ??????????? cl = c.getClassLoader();
    ??????????? System.out.println("java.lang.Object's loader is " + cl);
    ??????????? c = Class.forName("LoaderSample1");
    ??????????? cl = c.getClassLoader();
    ??????????? System.out.println("LoaderSample1's loader is " + cl);
    ??????? } catch (Exception e) {
    ??????????? e.printStackTrace();
    ??????? }
    ??? }
    }

    在我的機器上(Sun Java 1.4.2)的運行結果

    sun.misc.Launcher$AppClassLoader@1a0c10f
    sun.misc.Launcher$ExtClassLoader@e2eec8
    null 
    java.lang.Object's loader is null
    LoaderSample1's loader is sun.misc.Launcher$AppClassLoader@1a0c10f
    

    第一行表示,系統類裝載器實例化自類sun.misc.Launcher$AppClassLoader

    第二行表示,系統類裝載器的parent實例化自類sun.misc.Launcher$ExtClassLoader

    第三行表示,系統類裝載器parent的parent為bootstrap

    第四行表示,核心類java.lang.Object是由bootstrap裝載的

    第五行表示,用戶類LoaderSample1是由系統類裝載器裝載的

    parent delegation模型

    從1.2版本開始,Java引入了雙親委托模型,從而更好的保證Java平臺的安全。在此模型下,當一個裝載器被請求裝載某個類時,它首先委托自己的parent去裝載,若parent能裝載,則返回這個類所對應的Class對象,若parent不能裝載,則由parent的請求者去裝載。

    如圖1所示,loader2的parent為loader1,loader1的parent為system class loader。假設loader2被要求裝載類MyClass,在parent delegation模型下,loader2首先請求loader1代為裝載,loader1再請求系統類裝載器去裝載MyClass。若系統裝載器能成功裝載,則將MyClass所對應的Class對象的reference返回給loader1,loader1再將reference返回給loader2,從而成功將類MyClass裝載進虛擬機。若系統類裝載器不能裝載MyClass,loader1會嘗試裝載MyClass,若loader1也不能成功裝載,loader2會嘗試裝載。若所有的parent及loader2本身都不能裝載,則裝載失敗。

    若有一個能成功裝載,實際裝載的類裝載器被稱為定義類裝載器,所有能成功返回Class對象的裝載器(包括定義類裝載器)被稱為初始類裝載器。如圖1所示,假設loader1實際裝載了MyClass,則loader1為MyClass的定義類裝載器,loader2和loader1為MyClass的初始類裝載器。

    圖 1 parent delegation模型

    需要指出的是,Class Loader是對象,它的父子關系和類的父子關系沒有任何關系。一對父子loader可能實例化自同一個Class,也可能不是,甚至父loader實例化自子類,子loader實例化自父類。假設MyClassLoader繼承自ParentClassLoader,我們可以有如下父子loader:

    ClassLoader loader1 = new MyClassLoader();
    //參數 loader1 為 parent
    ClassLoader loader2 = new ParentClassLoader(loader1); 
    

    那么parent delegation模型為什么更安全了?因為在此模型下用戶自定義的類裝載器不可能裝載應該由父親裝載器裝載的可靠類,從而防止不可靠甚至惡意的代碼代替由父親裝載器裝載的可靠代碼。實際上,類裝載器的編寫者可以自由選擇不用把請求委托給parent,但正如上所說,會帶來安全的問題。

    命名空間及其作用

    每個類裝載器有自己的命名空間,命名空間由所有以此裝載器為創始類裝載器的類組成。不同命名空間的兩個類是不可見的,但只要得到類所對應的Class對象的reference,還是可以訪問另一命名空間的類。

    例2演示了一個命名空間的類如何使用另一命名空間的類。在例子中,LoaderSample2由系統類裝載器裝載,LoaderSample3由自定義的裝載器loader負責裝載,兩個類不在同一命名空間,但LoaderSample2得到了LoaderSample3所對應的Class對象的reference,所以它可以訪問LoaderSampl3中公共的成員(如age)。

    例2不同命名空間的類的訪問

    				/*LoaderSample2.java*/
    import java.net.*;
    import java.lang.reflect.*;
    public class LoaderSample2 {
    ??? public static void main(String[] args) {
    ??????? try {
    ??????????? String path = System.getProperty("user.dir");
    ??????????? URL[] us = {new URL("file://" + path + "/sub/")};
    ??????????? ClassLoader loader = new URLClassLoader(us);
    ??????????? Class c = loader.loadClass("LoaderSample3");
    ??????????? Object o = c.newInstance();
    ??????????? Field f = c.getField("age");
    ??????????? int age = f.getInt(o);
    ??????????? System.out.println("age is " + age);
    ??????? } catch (Exception e) {
    ??????????? e.printStackTrace();
    ??????? }
    ??? }
    				}
    		
    				/*sub/Loadersample3.java*/
    public class LoaderSample3 {
    ??? static {
    ??????? System.out.println("LoaderSample3 loaded");
    ??? }
    ??? public int age = 30;
    }

    編譯:javac LoaderSample2.java; javac sub/LoaderSample3.java

    運行:java LoaderSample2

    LoaderSample3 loaded
    age is 30
    

    從運行結果中可以看出,在類LoaderSample2中可以創建處于另一命名空間的類LoaderSample3中的對象并可以訪問其公共成員age。

    運行時包(runtime package)

    由同一類裝載器定義裝載的屬于相同包的類組成了運行時包,決定兩個類是不是屬于同一個運行時包,不僅要看它們的包名是否相同,還要看的定義類裝載器是否相同。只有屬于同一運行時包的類才能互相訪問包可見的類和成員。這樣的限制避免了用戶自己的代碼冒充核心類庫的類訪問核心類庫包可見成員的情況。假設用戶自己定義了一個類java.lang.Yes,并用用戶自定義的類裝載器裝載,由于java.lang.Yes和核心類庫java.lang.*由不同的裝載器裝載,它們屬于不同的運行時包,所以java.lang.Yes不能訪問核心類庫java.lang中類的包可見的成員。

    總結

    在簡單討論了類裝載器,parent delegation模型,命名空間,運行時包后,相信大家已經對它們的作用有了一定的了解。命名空間并沒有完全禁止屬于不同空間的類的互相訪問,雙親委托模型加強了Java的安全,運行時包增加了對包可見成員的保護。

    - 作者: hujunhain 2006年09月4日, 星期一 09:46  回復(0) |  引用(0)加入博采

    Java類裝載體系中的隔離性

    正文

    Java中類的查找與裝載出現的問題總是會時不時出現在Java程序員面前,這并不是什么丟臉的事情,相信沒有一個Java程序員沒遇到過ClassNotException,因此不要為被人瞅見自己也犯這樣的錯誤而覺得不自然,但是在如果出現了ClassNotFoundException后異常后一臉的茫然,那我想你該了解一下java的類裝載的體制了,同時為了進行下面的關于類裝載器之間的隔離性的討論,我們先簡單介紹一下類裝載的體系結構。

    1. Java類裝載體系結構

    裝載類的過程非常簡單:查找類所在位置,并將找到的Java類的字節碼裝入內存,生成對應的Class對象。Java的類裝載器專門用來實現這樣的過程,JVM并不止有一個類裝載器,事實上,如果你愿意的話,你可以讓JVM擁有無數個類裝載器,當然這除了測試JVM外,我想不出還有其他的用途。你應該已經發現到了這樣一個問題,類裝載器自身也是一個類,它也需要被裝載到內存中來,那么這些類裝載器由誰來裝載呢,總得有個根吧?沒錯,確實存在這樣的根,它就是神龍見首不見尾的Bootstrap ClassLoader. 為什么說它神龍見首不見尾呢,因為你根本無法在Java代碼中抓住哪怕是它的一點點的尾巴,盡管你能時時刻刻體會到它的存在,因為java的運行環境所需要的所有類庫,都由它來裝載,而它本身是C++寫的程序,可以獨立運行,可以說是JVM的運行起點,偉大吧。在Bootstrap完成它的任務后,會生成一個AppClassLoader(實際上之前系統還會使用擴展類裝載器ExtClassLoader,它用于裝載Java運行環境擴展包中的類),這個類裝載器才是我們經常使用的,可以調用ClassLoader.getSystemClassLoader() 來獲得,我們假定程序中沒有使用類裝載器相關操作設定或者自定義新的類裝載器,那么我們編寫的所有java類通通會由它來裝載,值得尊敬吧。AppClassLoader查找類的區域就是耳熟能詳的Classpath,也是初學者必須跨過的門檻,有沒有靈光一閃的感覺,我們按照它的類查找范圍給它取名為類路徑類裝載器。還是先前假定的情況,當Java中出現新的類,AppClassLoader首先在類傳遞給它的父類類裝載器,也就是Extion ClassLoader,詢問它是否能夠裝載該類,如果能,那AppClassLoader就不干這活了,同樣Extion ClassLoader在裝載時,也會先問問它的父類裝載器。我們可以看出類裝載器實際上是一個樹狀的結構圖,每個類裝載器有自己的父親,類裝載器在裝載類時,總是先讓自己的父類裝載器裝載(多么尊敬長輩),如果父類裝載器無法裝載該類時,自己就會動手裝載,如果它也裝載不了,那么對不起,它會大喊一聲:Exception,class not found。有必要提一句,當由直接使用類路徑裝載器裝載類失敗拋出的是NoClassDefFoundException異常。如果使用自定義的類裝載器loadClass方法或者ClassLoader的findSystemClass方法裝載類,如果你不去刻意改變,那么拋出的是ClassNotFoundException。

    我們簡短總結一下上面的討論:

    1.JVM類裝載器的體系結構可以看作是樹狀結構。

    2.父類裝載器優先裝載。在父類裝載器裝載失敗的情況下再裝載,如果都裝載失敗則拋出ClassNotFoundException或者NoClassDefFoundError異常。

    那么我們的類在什么情況下被裝載的呢?

    2. 類如何被裝載

    在java2中,JVM是如何裝載類的呢,可以分為兩種類型,一種是隱式的類裝載,一種式顯式的類裝載。

    2.1 隱式的類裝載

    隱式的類裝載是編碼中最常用得方式:

    A b = new A();
    

    如果程序運行到這段代碼時還沒有A類,那么JVM會請求裝載當前類的類裝器來裝載類。問題來了,我把代碼弄得復雜一點點,但依舊沒有任何難度,請思考JVM得裝載次序:

    package test;
    Public class A{
        public void static main(String args[]){
            B b = new B();
        }
    }
    
    class B{C c;}
    
    class C{}
    

    揭曉答案,類裝載的次序為A->B,而類C根本不會被JVM理會,先不要驚訝,仔細想想,這不正是我們最需要得到的結果。我們仔細了解一下JVM裝載順序。當使用Java A命令運行A類時,JVM會首先要求類路徑類裝載器(AppClassLoader)裝載A類,但是這時只裝載A,不會裝載A中出現的其他類(B類),接著它會調用A中的main函數,直到運行語句b = new B()時,JVM發現必須裝載B類程序才能繼續運行,于是類路徑類裝載器會去裝載B類,雖然我們可以看到B中有有C類的聲明,但是并不是實際的執行語句,所以并不去裝載C類,也就是說JVM按照運行時的有效執行語句,來決定是否需要裝載新類,從而裝載盡可能少的類,這一點和編譯類是不相同的。

    2.2 顯式的類裝載

    使用顯示的類裝載方法很多,我們都裝載類test.A為例。

    使用Class類的forName方法。它可以指定裝載器,也可以使用裝載當前類的裝載器。例如:

    Class.forName("test.A");
    它的效果和
    Class.forName("test.A",true,this.getClass().getClassLoader());
    是一樣的。
    

    使用類路徑類裝載裝載.

    ClassLoader.getSystemClassLoader().loadClass("test.A");
    

    使用當前進程上下文的使用的類裝載器進行裝載,這種裝載類的方法常常被有著復雜類裝載體系結構的系統所使用。

    Thread.currentThread().getContextClassLoader().loadClass("test.A")
    

    使用自定義的類裝載器裝載類

    public class MyClassLoader extends URLClassLoader{
    public MyClassLoader() {
            super(new URL[0]);
        }
    }
    MyClassLoader myClassLoader = new MyClassLoader();
    myClassLoader.loadClass("test.A");
    

    MyClassLoader繼承了URLClassLoader類,這是JDK核心包中的類裝載器,在沒有指定父類裝載器的情況下,類路徑類裝載器就是它的父類裝載器,MyClassLoader并沒有增加類的查找范圍,因此它和類路徑裝載器有相同的效果。

    我們已經知道Java的類裝載器體系結構為樹狀,多個類裝載器可以指定同一個類裝載器作為自己的父類,每個子類裝載器就是樹狀結構的一個分支,當然它們又可以個有子類裝載器類裝載器,類裝載器也可以沒有父類裝載器,這時Bootstrap類裝載器將作為它的隱含父類,實際上Bootstrap類裝載器是所有類裝載器的祖先,也是樹狀結構的根。這種樹狀體系結構,以及父類裝載器優先的機制,為我們編寫自定義的類裝載器提供了便利,同時可以讓程序按照我們希望的方式進行類的裝載。例如某個程序的類裝載器體系結構圖如下:

    圖2:某個程序的類裝載器的結構

    解釋一下上面的圖,ClassLoaderA為自定義的類裝載器,它的父類裝載器為類路徑裝載器,它有兩個子類裝載器ClassLoaderAA和ClassLaderAB,ClassLoaderB為程序使用的另外一個類裝載器,它沒有父類裝載器,但有一個子類裝載器ClassLoaderBB。你可能會說,見鬼,我的程序怎么會使用這么復雜的類裝載器結構。為了進行下面的討論,暫且委屈一下。

    3. 奇怪的隔離性

    我們不難發現,圖2中的類裝載器AA和AB, AB和BB,AA和B等等位于不同分支下,他們之間沒有父子關系,我不知道如何定義這種關系,姑且稱他們位于不同分支下。兩個位于不同分支的類裝載器具有隔離性,這種隔離性使得在分別使用它們裝載同一個類,也會在內存中出現兩個Class類的實例。因為被具有隔離性的類裝載器裝載的類不會共享內存空間,使得使用一個類裝載器不可能完成的任務變得可以輕而易舉,例如類的靜態變量可能同時擁有多個值(雖然好像作用不大),因為就算是被裝載類的同一靜態變量,它們也將被保存不同的內存空間,又例如程序需要使用某些包,但又不希望被程序另外一些包所使用,很簡單,編寫自定義的類裝載器。類裝載器的這種隔離性在許多大型的軟件應用和服務程序得到了很好的應用。下面是同一個類靜態變量為不同值的例子。

    package test;
    public class A {
      public static void main( String[] args ) {
        try {
          //定義兩個類裝載器
          MyClassLoader aa= new MyClassLoader();
          MyClassLoader bb = new MyClassLoader();
    
          //用類裝載器aa裝載testb.B類
          Class clazz=aa.loadClass("testb. B");
          Constructor constructor= 
            clazz.getConstructor(new Class[]{Integer.class});
          Object object = 
    	    constructor.newInstance(new Object[]{new Integer(1)});
          Method method = 
    	    clazz.getDeclaredMethod("printB",new Class[0]);
    
          //用類裝載器bb裝載testb.B類
          Class clazz2=bb.loadClass("testb. B");
          Constructor constructor2 = 
            clazz2.getConstructor(new Class[]{Integer.class});
          Object object2 = 
    	    constructor2.newInstance(new Object[]{new Integer(2)});
          Method method2 = 
    	    clazz2.getDeclaredMethod("printB",new Class[0]);
    
          //顯示test.B中的靜態變量的值 
          method.invoke( object,new Object[0]);
          method2.invoke( object2,new Object[0]);
        } catch ( Exception e ) {
          e.printStackTrace();
        }
      }
    }
    

    //Class B 必須位于MyClassLoader的查找范圍內,
    //而不應該在MyClassLoader的父類裝載器的查找范圍內。
    package testb;
    public class B {
        static int b ;
    
        public B(Integer testb) {
            b = testb.intValue();
        }
    
        public void printB() {
            System.out.print("my static field b is ", b);
        }
    }
    

    public class MyClassLoader extends URLClassLoader{
      private static File file = new File("c:\\classes ");
      //該路徑存放著class B,但是沒有class A
    
      public MyClassLoader() {
        super(getUrl());
      }
    
      public static URL[] getUrl() {
        try {
          return new URL[]{file.toURL()};
        } catch ( MalformedURLException e ) {
          return new URL[0];
        }
      }
    }
    

    程序的運行結果為:

    my static field b is 1
    my static field b is 2
    

    程序的結果非常有意思,從編程者的角度,我們甚至可以把不在同一個分支的類裝載器看作不同的java虛擬機,因為它們彼此覺察不到對方的存在。程序在使用具有分支的類裝載的體系結構時要非常小心,弄清楚每個類裝載器的類查找范圍,盡量避免父類裝載器和子類裝載器的類查找范圍中有相同類名的類(包括包名和類名),下面這個例子就是用來說明這種情況可能帶來的問題。

    假設有相同名字卻不同版本的接口 A,

    版本 1:
    package test;
    Intefer Same{ public String getVersion(); }
    版本 2:
    Package test;
    Intefer Same{ public String getName(); }
    

    接口A兩個版本的實現:

    版本1的實現
    package test;
    public class Same1Impl implements Same {
    public String getVersion(){ return "A version 1";}
    }
    版本2的實現
    public class Same 2Impl implements Same {
    public String getName(){ return "A version 2";}
    }
    

    我們依然使用圖2的類裝載器結構,首先將版本1的Same和Same的實現類Same1Impl打成包same1.jar,將版本2的Same和Same的實現類Same1Impl打成包same2.jar?,F在,做這樣的事情,把same1.jar放入類裝載器ClassLoaderA的類查找范圍中,把same2.jar放入類裝器ClassLoaderAB的類查找范圍中。當你興沖沖的運行下面這個看似正確的程序。

    實際上這個錯誤的是由父類載器優先裝載的機制造成,當類裝載器ClassLoaderAB在裝載Same2Impl類時發現必須裝載接口test.Same,于是按規定請求父類裝載器裝載,父類裝載器發現了版本1的test.Same接口并興沖沖的裝載,但是卻想不到Same2Impl所希望的是版本2 的test.Same,后面的事情可想而知了,異常被拋出。

    我們很難責怪Java中暫時并沒有提供區分版本的機制,如果使用了比較復雜的類裝載器體系結構,在出現了某個包或者類的多個版本時,應特別注意。

    掌握和靈活運用Java的類裝載器的體系結構,對程序的系統設計,程序的實現,已經程序的調試,都有相當大的幫助。希望以上的內容能夠對您有所幫助。

    posted on 2007-01-19 18:22 ???MengChuChen 閱讀(743) 評論(0)  編輯  收藏

    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    主站蜘蛛池模板: 免费无码一区二区三区蜜桃| 国产在线a免费观看| 国产免费拔擦拔擦8X高清在线人 | 免费又黄又硬又爽大片| 亚洲最大福利视频网站| 色欲aⅴ亚洲情无码AV| 亚洲精品视频免费在线观看| 亚洲国产日韩在线观频| 亚洲国产精品综合久久20| a毛片免费在线观看| 免费看国产一级片| 亚洲欧洲日韩国产一区二区三区| 99免费精品视频| 久久亚洲精品国产精品黑人| 在线观看亚洲免费| 麻豆一区二区免费播放网站| 亚洲天堂2017无码中文| 国产一区二区三区免费在线观看| 亚洲av永久无码精品网址| 久热中文字幕在线精品免费| 国产亚洲美女精品久久久久狼| 亚洲狠狠婷婷综合久久| 国产又大又粗又长免费视频| 狠狠色伊人亚洲综合成人| 无码国产精品一区二区免费模式| 激情综合色五月丁香六月亚洲| 国产99久久亚洲综合精品| 久久久久亚洲AV成人网人人网站| 搜日本一区二区三区免费高清视频| 精品久久洲久久久久护士免费| 亚洲免费福利视频| 在线永久免费的视频草莓| 久久久久亚洲精品无码网址色欲| 77777亚洲午夜久久多人| 色影音免费色资源| 美女视频黄频a免费大全视频| 日本人护士免费xxxx视频| 亚洲看片无码在线视频 | 国产成人精品亚洲精品| 日韩毛片在线免费观看| 韩国免费三片在线视频|