一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

classLoader動態(tài)加載

 moonboat 2010-11-18

classLoader動態(tài)加載

文章分類:Java編程 當(dāng)JVM(Java虛擬機)啟動時,會形成由三個類加載器組成的初始類加載器層次結(jié)構(gòu):

        bootstrap classloader
                 |
        extension classloader
                 |
        system classloader

bootstrap classloader - 引導(dǎo)(也稱為原始)類加載器,它負(fù)責(zé)加載Java的核心類。在Sun的JVM中,在執(zhí)行java的命令中使用-Xbootclasspath選項或使用 -D選項指定sun.boot.class.path系統(tǒng)屬性值可以指定附加的類。這個加載器的是非常特殊的,它實際上不是 java.lang.ClassLoader的子類,而是由JVM自身實現(xiàn)的。大家可以通過執(zhí)行以下代碼來獲得bootstrap classloader加載了那些核心類庫:
    URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
    for (int i = 0; i < urls.length; i++) {
      System.out.println(urls[i].toExternalForm());
    }
在我的計算機上的結(jié)果為:
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/dom.jar
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/sax.jar
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xalan-2.3.1.jar
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xercesImpl-2.0.0.jar
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xml-apis.jar
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xsltc.jar
file:/C:/j2sdk1.4.1_01/jre/lib/rt.jar
file:/C:/j2sdk1.4.1_01/jre/lib/i18n.jar
file:/C:/j2sdk1.4.1_01/jre/lib/sunrsasign.jar
file:/C:/j2sdk1.4.1_01/jre/lib/jsse.jar
file:/C:/j2sdk1.4.1_01/jre/lib/jce.jar
file:/C:/j2sdk1.4.1_01/jre/lib/charsets.jar
file:/C:/j2sdk1.4.1_01/jre/classes
這時大家知道了為什么我們不需要在系統(tǒng)屬性CLASSPATH中指定這些類庫了吧,因為JVM在啟動的時候就自動加載它們了。

extension classloader - 擴展類加載器,它負(fù)責(zé)加載JRE的擴展目錄(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系統(tǒng)屬性指定的)中JAR的類 包。這為引入除Java核心類以外的新功能提供了一個標(biāo)準(zhǔn)機制。因為默認(rèn)的擴展目錄對所有從同一個JRE中啟動的JVM都是通用的,所以放入這個目錄的 JAR類包對所有的JVM和system classloader都是可見的。在這個實例上調(diào)用方法getParent()總是返回空值null,因為引導(dǎo)加載器bootstrap classloader不是一個真正的ClassLoader實例。所以當(dāng)大家執(zhí)行以下代碼時:
    System.out.println(System.getProperty("java.ext.dirs"));
    ClassLoader extensionClassloader=ClassLoader.getSystemClassLoader().getParent();
    System.out.println("the parent of extension classloader : "+extensionClassloader.getParent());
結(jié)果為:
C:\j2sdk1.4.1_01\jre\lib\ext
the parent of extension classloader : null
extension classloader是system classloader的parent,而bootstrap classloader是extension classloader的parent,但它不是一個實際的classloader,所以為null。

system classloader - 系統(tǒng)(也稱為應(yīng)用)類加載器,它負(fù)責(zé)在JVM被啟動時,加載來自在命令java中的-classpath或者java.class.path系統(tǒng)屬性或者 CLASSPATH操作系統(tǒng)屬性所指定的JAR類包和類路徑??偰芡ㄟ^靜態(tài)方法ClassLoader.getSystemClassLoader()找 到該類加載器。如果沒有特別指定,則用戶自定義的任何類加載器都將該類加載器作為它的父加載器。執(zhí)行以下代碼即可獲得:
    System.out.println(System.getProperty("java.class.path"));
輸出結(jié)果則為用戶在系統(tǒng)屬性里面設(shè)置的CLASSPATH。
classloader加載類用的是全盤負(fù)責(zé)委托機制。所謂全盤負(fù)責(zé),即是當(dāng)一個classloader加載一個Class的時候,這個 Class所依賴的和引用的所有Class也由這個classloader負(fù)責(zé)載入,除非是顯式的使用另外一個classloader載入;委托機制則是 先讓parent(父)類加載器(而不是super,它與parent classloader類不是繼承關(guān)系)尋找,只有在parent找不到的時候才從自己的類路徑中去尋找。此外類加載還采用了cache機制,也就是如果 cache中保存了這個Class就直接返回它,如果沒有才從文件中讀取和轉(zhuǎn)換成Class,并存入cache,這就是為什么我們修改了Class但是必 須重新啟動JVM才能生效的原因。


每個ClassLoader加載Class的過程是:
1.檢測此Class是否載入過(即在cache中是否有此Class),如果有到8,如果沒有到2
2.如果parent classloader不存在(沒有parent,那parent一定是bootstrap classloader了),到4
3.請求parent classloader載入,如果成功到8,不成功到5
4.請求jvm從bootstrap classloader中載入,如果成功到8
5.尋找Class文件(從與此classloader相關(guān)的類路徑中尋找)。如果找不到則到7.
6.從文件中載入Class,到8.
7.拋出ClassNotFoundException.
8.返回Class.

其中5.6步我們可以通過覆蓋ClassLoader的findClass方法來實現(xiàn)自己的載入策略。甚至覆蓋loadClass方法來實現(xiàn)自己的載入過程。

類加載器的順序是:
先是bootstrap classloader,然后是extension classloader,最后才是system classloader。大家會發(fā)現(xiàn)加載的Class越是重要的越在靠前面。這樣做的原因是出于安全性的考慮,試想如果system classloader“親自”加載了一個具有破壞性的“java.lang.System”類的后果吧。這種委托機制保證了用戶即使具有一個這樣的類, 也把它加入到了類路徑中,但是它永遠(yuǎn)不會被載入,因為這個類總是由bootstrap classloader來加載的。大家可以執(zhí)行一下以下的代碼:
    System.out.println(System.class.getClassLoader());
將會看到結(jié)果是null,這就表明java.lang.System是由bootstrap classloader加載的,因為bootstrap classloader不是一個真正的ClassLoader實例,而是由JVM實現(xiàn)的,正如前面已經(jīng)說過的。

下面就讓我們來看看JVM是如何來為我們來建立類加載器的結(jié)構(gòu)的:
sun.misc.Launcher,顧名思義,當(dāng)你執(zhí)行java命令的時候,JVM會先使用bootstrap classloader載入并初始化一個Launcher,執(zhí)行下來代碼:
   System.out.println("the Launcher's classloader is "+sun.misc.Launcher.getLauncher().getClass().getClassLoader());
結(jié)果為:
   the Launcher's classloader is null (因為是用bootstrap classloader加載,所以class loader為null)
Launcher會根據(jù)系統(tǒng)和命令設(shè)定初始化好class loader結(jié)構(gòu),JVM就用它來獲得extension classloader和system classloader,并載入所有的需要載入的Class,最后執(zhí)行java命令指定的帶有靜態(tài)的main方法的Class。extension classloader實際上是sun.misc.Launcher$ExtClassLoader類的一個實例,system classloader實際上是sun.misc.Launcher$AppClassLoader類的一個實例。并且都是 java.net.URLClassLoader的子類。

讓我們來看看Launcher初試化的過程的部分代碼。

Launcher的部分代碼:
public class Launcher  {
    public Launcher() {
        ExtClassLoader extclassloader;
        try {
            //初始化extension classloader
            extclassloader = ExtClassLoader.getExtClassLoader();
        } catch(IOException ioexception) {
            throw new InternalError("Could not create extension class loader");
        }
        try {
            //初始化system classloader,parent是extension classloader
            loader = AppClassLoader.getAppClassLoader(extclassloader);
        } catch(IOException ioexception1) {
            throw new InternalError("Could not create application class loader");
        }
        //將system classloader設(shè)置成當(dāng)前線程的context classloader(將在后面加以介紹)
        Thread.currentThread().setContextClassLoader(loader);
        ......
    }
    public ClassLoader getClassLoader() {
        //返回system classloader
        return loader;
    }
}

extension classloader的部分代碼:
static class Launcher$ExtClassLoader extends URLClassLoader {

    public static Launcher$ExtClassLoader getExtClassLoader()
        throws IOException
    {
        File afile[] = getExtDirs();
        return (Launcher$ExtClassLoader)AccessController.doPrivileged(new Launcher$1(afile));
    }
   private static File[] getExtDirs() {
        //獲得系統(tǒng)屬性“java.ext.dirs”
        String s = System.getProperty("java.ext.dirs");
        File afile[];
        if(s != null) {
            StringTokenizer stringtokenizer = new StringTokenizer(s, File.pathSeparator);
            int i = stringtokenizer.countTokens();
            afile = new File[i];
            for(int j = 0; j < i; j++)
                afile[j] = new File(stringtokenizer.nextToken());

        } else {
            afile = new File[0];
        }
        return afile;
    }
}

system classloader的部分代碼:
static class Launcher$AppClassLoader extends URLClassLoader
{

    public static ClassLoader getAppClassLoader(ClassLoader classloader)
        throws IOException
    {
        //獲得系統(tǒng)屬性“java.class.path”
        String s = System.getProperty("java.class.path");
        File afile[] = s != null ? Launcher.access$200(s) : new File[0];
        return (Launcher$AppClassLoader)AccessController.doPrivileged(new Launcher$2(s, afile, classloader));
    }
}

看了源代碼大家就清楚了吧,extension classloader是使用系統(tǒng)屬性“java.ext.dirs”設(shè)置類搜索路徑的,并且沒有parent。system classloader是使用系統(tǒng)屬性“java.class.path”設(shè)置類搜索路徑的,并且有一個parent classloader。Launcher初始化extension classloader,system classloader,并將system classloader設(shè)置成為context classloader,但是僅僅返回system classloader給JVM。

  這里怎么又出來一個context classloader呢?它有什么用呢?我們在建立一個線程Thread的時候,可以為這個線程通過setContextClassLoader方法來 指定一個合適的classloader作為這個線程的context classloader,當(dāng)此線程運行的時候,我們可以通過getContextClassLoader方法來獲得此context classloader,就可以用它來載入我們所需要的Class。默認(rèn)的是system classloader。利用這個特性,我們可以“打破”classloader委托機制了,父classloader可以獲得當(dāng)前線程的context classloader,而這個context classloader可以是它的子classloader或者其他的classloader,那么父classloader就可以從其獲得所需的 Class,這就打破了只能向父classloader請求的限制了。這個機制可以滿足當(dāng)我們的classpath是在運行時才確定,并由定制的 classloader加載的時候,由system classloader(即在jvm classpath中)加載的class可以通過context classloader獲得定制的classloader并加載入特定的class(通常是抽象類和接口,定制的classloader中是其實現(xiàn)),例 如web應(yīng)用中的servlet就是用這種機制加載的.


好了,現(xiàn)在我們了解了classloader的結(jié)構(gòu)和工作原理,那么我們?nèi)绾螌崿F(xiàn)在運行時的動態(tài)載入和更新呢?只要我們能夠動態(tài)改變類搜索路徑和 清除classloader的cache中已經(jīng)載入的Class就行了,有兩個方案,一是我們繼承一個classloader,覆蓋loadclass方 法,動態(tài)的尋找Class文件并使用defineClass方法來;另一個則非常簡單實用,只要重新使用一個新的類搜索路徑來new一個 classloader就行了,這樣即更新了類搜索路徑以便來載入新的Class,也重新生成了一個空白的cache(當(dāng)然,類搜索路徑不一定必須更 改)。噢,太好了,我們幾乎不用做什么工作,java.netURLClassLoader正是一個符合我們要求的classloader!我們可以直接 使用或者繼承它就可以了!

這是j2se1.4 API的doc中URLClassLoader的兩個構(gòu)造器的描述:
URLClassLoader(URL[] urls)
          Constructs a new URLClassLoader for the specified URLs using the default delegation parent ClassLoader.
URLClassLoader(URL[] urls, ClassLoader parent)
          Constructs a new URLClassLoader for the given URLs.
其中URL[] urls就是我們要設(shè)置的類搜索路徑,parent就是這個classloader的parent classloader,默認(rèn)的是system classloader。


好,現(xiàn)在我們能夠動態(tài)的載入Class了,這樣我們就可以利用newInstance方法來獲得一個Object。但我們?nèi)绾螌⒋薕bject造型呢?可以將此Object造型成它本身的Class嗎?

首先讓我們來分析一下java源文件的編譯,運行吧!javac命令是調(diào)用“JAVA_HOME/lib/tools.jar”中的“com.sun.tools.javac.Main”的compile方法來編譯:

    public static int compile(String as[]);

    public static int compile(String as[], PrintWriter printwriter);

返回0表示編譯成功,字符串?dāng)?shù)組as則是我們用javac命令編譯時的參數(shù),以空格劃分。例如:
javac -classpath c:\foo\bar.jar;. -d c:\ c:\Some.java
則字符串?dāng)?shù)組as為{"-classpath","c:\\foo\\bar.jar;.","-d","c:\\","c: \\Some.java"},如果帶有PrintWriter參數(shù),則會把編譯信息出到這個指定的printWriter中。默認(rèn)的輸出是 System.err。

其中Main是由JVM使用Launcher初始化的system classloader載入的,根據(jù)全盤負(fù)責(zé)原則,編譯器在解析這個java源文件時所發(fā)現(xiàn)的它所依賴和引用的所有Class也將由system classloader載入,如果system classloader不能載入某個Class時,編譯器將拋出一個“cannot resolve symbol”錯誤。

所以首先編譯就通不過,也就是編譯器無法編譯一個引用了不在CLASSPATH中的未知Class的java源文件,而由于拼寫錯誤或者沒有把所需類庫放到CLASSPATH中,大家一定經(jīng)常看到這個“cannot resolve symbol”這個編譯錯誤吧!

其次,就是我們把這個Class放到編譯路徑中,成功的進行了編譯,然后在運行的時候不把它放入到CLASSPATH中而利用我們自己的 classloader來動態(tài)載入這個Class,這時候也會出現(xiàn)“java.lang.NoClassDefFoundError”的違例,為什么呢?

我們再來分析一下,首先調(diào)用這個造型語句的可執(zhí)行的Class一定是由JVM使用Launcher初始化的system classloader載入的,根據(jù)全盤負(fù)責(zé)原則,當(dāng)我們進行造型的時候,JVM也會使用system classloader來嘗試載入這個Class來對實例進行造型,自然在system classloader尋找不到這個Class時就會拋出“java.lang.NoClassDefFoundError”的違例。

OK,現(xiàn)在讓我們來總結(jié)一下,java文件的編譯和Class的載入執(zhí)行,都是使用Launcher初始化的system classloader作為類載入器的,我們無法動態(tài)的改變system classloader,更無法讓JVM使用我們自己的classloader來替換system classloader,根據(jù)全盤負(fù)責(zé)原則,就限制了編譯和運行時,我們無法直接顯式的使用一個system classloader尋找不到的Class,即我們只能使用Java核心類庫,擴展類庫和CLASSPATH中的類庫中的Class。

還不死心!再嘗試一下這種情況,我們把這個Class也放入到CLASSPATH中,讓system classloader能夠識別和載入。然后我們通過自己的classloader來從指定的class文件中載入這個Class(不能夠委托 parent載入,因為這樣會被system classloader從CLASSPATH中將其載入),然后實例化一個Object,并造型成這個Class,這樣JVM也識別這個Class(因為 system classloader能夠定位和載入這個Class從CLASSPATH中),載入的也不是CLASSPATH中的這個Class,而是從 CLASSPATH外動態(tài)載入的,這樣總行了吧!十分不幸的是,這時會出現(xiàn)“java.lang.ClassCastException”違例。

為什么呢?我們也來分析一下,不錯,我們雖然從CLASSPATH外使用我們自己的classloader動態(tài)載入了這個Class,但將它的實 例造型的時候是JVM會使用system classloader來再次載入這個Class,并嘗試將使用我們的自己的classloader載入的Class的一個實例造型為system classloader載入的這個Class(另外的一個)。大家發(fā)現(xiàn)什么問題了嗎?也就是我們嘗試將從一個classloader載入的Class的一 個實例造型為另外一個classloader載入的Class,雖然這兩個Class的名字一樣,甚至是從同一個class文件中載入。但不幸的是JVM 卻認(rèn)為這個兩個Class是不同的,即JVM認(rèn)為不同的classloader載入的相同的名字的Class(即使是從同一個class文件中載入的)是 不同的!這樣做的原因我想大概也是主要出于安全性考慮,這樣就保證所有的核心Java類都是system

classloader載入的,我們無法用自己的classloader載入的相同名字的Class的實例來替換它們的實例。

看到這里,聰明的讀者一定想到了該如何動態(tài)載入我們的Class,實例化,造型并調(diào)用了吧!

那就是利用面向?qū)ο蟮幕咎匦灾坏亩嘈涡?。我們把我們動態(tài)載入的Class的實例造型成它的一個system classloader所能識別的父類就行了!這是為什么呢?我們還是要再來分析一次。當(dāng)我們用我們自己的classloader來動態(tài)載入這我們只要把 這個Class的時候,發(fā)現(xiàn)它有一個父類Class,在載入它之前JVM先會載入這個父類Class,這個父類Class是system classloader所能識別的,根據(jù)委托機制,它將由system classloader載入,然后我們的classloader再載入這個Class,創(chuàng)建一個實例,造型為這個父類Class,注意了,造型成這個父類 Class的時候(也就是上溯)是面向?qū)ο蟮膉ava語言所允許的并且JVM也支持的,JVM就使用system classloader再次載入這個父類Class,然后將此實例造型為這個父類Class。大家可以從這個過程發(fā)現(xiàn)這個父類Class都是由 system classloader載入的,也就是同一個class loader載入的同一個Class,所以造型的時候不會出現(xiàn)任何異常。而根據(jù)多形性,調(diào)用這個父類的方法時,真正執(zhí)行的是這個Class(非父類 Class)的覆蓋了父類方法的方法。這些方法中也可以引用system classloader不能識別的Class,因為根據(jù)全盤負(fù)責(zé)原則,只要載入這個Class的classloader即我們自己定義的 classloader能夠定位和載入這些Class就行了。

這樣我們就可以事先定義好一組接口或者基類并放入CLASSPATH中,然后在執(zhí)行的時候動態(tài)的載入實現(xiàn)或者繼承了這些接口或基類的子類。還不明 白嗎?讓我們來想一想Servlet吧,web application server能夠載入任何繼承了Servlet的Class并正確的執(zhí)行它們,不管它實際的Class是什么,就是都把它們實例化成為一個Servlet Class,然后執(zhí)行Servlet的init,doPost,doGet和destroy等方法的,而不管這個Servlet是從web- inf/lib和web-inf/classes下由system classloader的子classloader(即定制的classloader)動態(tài)載入。說了這么多希望大家都明白了。在applet,ejb等 容器中,都是采用了這種機制.

對于以上各種情況,希望大家實際編寫一些example來實驗一下。

最后我再說點別的,classloader雖然稱為類加載器,但并不意味著只能用來加載Class,我們還可以利用它也獲得圖片,音頻文件等資源 的URL,當(dāng)然,這些資源必須在CLASSPATH中的jar類庫中或目錄下。我們來看API的doc中關(guān)于ClassLoader的兩個尋找資源和 Class的方法描述吧:
        public URL getResource(String name)
        用指定的名字來查找資源,一個資源是一些能夠被class代碼訪問的在某種程度上依賴于代碼位置的數(shù)據(jù)(圖片,音頻,文本等等)。
                一個資源的名字是以'/'號分隔確定資源的路徑名的。
                這個方法將先請求parent classloader搜索資源,如果沒有parent,則會在內(nèi)置在虛擬機中的classloader(即bootstrap classloader)的路徑中搜索。如果失敗,這個方法將調(diào)用findResource(String)來尋找資源。
        public static URL getSystemResource(String name)
                從用來載入類的搜索路徑中查找一個指定名字的資源。這個方法使用system class loader來定位資源。即相當(dāng)于ClassLoader.getSystemClassLoader().getResource(name)。

例如:
    System.out.println(ClassLoader.getSystemResource("java/lang/String.class"));
的結(jié)果為:
    jar:file:/C:/j2sdk1.4.1_01/jre/lib/rt.jar!/java/lang/String.class
表明String.class文件在rt.jar的java/lang目錄中。
因此我們可以將圖片等資源隨同Class一同打包到j(luò)ar類庫中(當(dāng)然,也可單獨打包這些資源)并添加它們到class loader的搜索路徑中,我們就可以無需關(guān)心這些資源的具體位置,讓class loader來幫我們尋找了!

講了這么多,希望大家能夠明白java classloader的載入機制和載入規(guī)則,更希望能夠?qū)Υ蠹乙院蟮拈_發(fā)有所幫助. ^_^

關(guān)于class loader的一些資料:

http://dev.csdn.net/article/60/60806.shtm

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    黄色日韩欧美在线观看| 日本和亚洲的香蕉视频| 特黄大片性高水多欧美一级| 午夜亚洲精品理论片在线观看| 日韩日韩欧美国产精品| 国产成人在线一区二区三区| 九九热视频经典在线观看| 老司机精品在线你懂的| 五月婷婷六月丁香亚洲| 日韩黄色一级片免费收看| 丁香六月啪啪激情综合区| 高清国产日韩欧美熟女| 日本午夜免费啪视频在线| 91亚洲精品综合久久| 亚洲精品成人午夜久久| 国产午夜福利在线观看精品| 99久免费精品视频在线观| 少妇熟女精品一区二区三区| 国产一区二区不卡在线视频| 亚洲天堂精品一区二区| 精品丝袜一区二区三区性色| 亚洲国产四季欧美一区| 老司机激情五月天在线不卡| 少妇人妻一级片一区二区三区 | 精品日韩视频在线观看| 国产综合欧美日韩在线精品| 日本欧美一区二区三区高清| 好吊视频一区二区在线| 日本欧美视频在线观看免费| 日韩欧美高清国内精品| 国产一区二区三区免费福利| 黑人巨大精品欧美一区二区区| 麻豆看片麻豆免费视频| 日韩欧美91在线视频| 欧美黑人精品一区二区在线 | 亚洲熟女国产熟女二区三区| 欧美激情床戏一区二区三| 欧美日韩精品一区二区三区不卡| 麻豆看片麻豆免费视频| 国产一级二级三级观看| 樱井知香黑人一区二区|