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

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

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

    牙牙窩

    BlogJava 聯(lián)系 聚合 管理
      8 Posts :: 21 Stories :: 10 Comments :: 0 Trackbacks

    JAVA 類動態(tài)載入的實現(xiàn)

    1 ?????? 前言

    前段時間因為項目的需要,我搞了一套類似 Servlet 重新載入的一個框架,實現(xiàn)了類的動態(tài)載入過程。本文寫了一些我的學(xué)習(xí)成果以及心得供大家分享一下。

    2 ?????? 類載入的原理

    (下面引用網(wǎng)上的一篇文章):

    當(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
    .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;
    ??????????? 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)然 , 類搜索路徑不一定必須更改 )

    (結(jié)束)

    ?

    以上所述,想必大家對 Jvm 類載入的原理有了一定的了解,大致也猜到實現(xiàn)的方法了吧。

    ?

    3 ?????? 實現(xiàn)類的動態(tài)載入

    結(jié)合上面說到,要 JVM 重新載入一個類,一共有 3 種辦法:(我加了一種)

    1. ??????? 重新寫一個新的路徑,更換包名或類名都是可行的方法。

    2. ??????? 自己寫一個類的尋找機制取代 SystemClassLoader 中的,再調(diào)用 defineClass 方法。

    3. ??????? 覆蓋 loadClass 方法,自己實現(xiàn)一個類載入過程。

    ?

    顯而易見,第一種方法是我自己添加上去的,而且應(yīng)該是用得最多得方法,因為在對 JVM 的類搜索、載入機制不是很熟悉的前提下,只要做一些簡單的修改就能達(dá)到所要的效果。(至少在我的項目中,這是首選,因為不會出錯)

    但如果第一種方法能實現(xiàn),本文就沒有寫的意義了。在一個 7 x 24 的大型系統(tǒng)中(如電信,銀行等),用第一種方法,就凸現(xiàn)出它最大問題,就是代碼變得無比龐大。因為每一個類的名字不同導(dǎo)致相同功能的類具有兩個或以上的類文件存在,對代碼的開發(fā)和維護(hù)來說,這是非常嚴(yán)重的問題。

    好,現(xiàn)在又退回到開始,排除掉第一種方法,如果使用第三種方法,應(yīng)該是可控性最高的方法,但本人才疏學(xué)淺,沒有把握把整個類載入過程摸透,因此還是排除的第三種方法。

    剩下只有第二種方法了。在 ClassLoader 中, defineClass 方法是比較好理解的,只要傳入類的字節(jié)流即可,因此,我只要重寫一次類文件的載入。下面是我的實現(xiàn)代碼:

    public class testClassLoader extends ClassLoader {

    ??? private static testClassLoader cl = null;

    ??? private static boolean flag = true;

    ??? private InputStream classFile = null;

    ??? private String name = null;

    ??

    ??? /**

    ???? * @param name String? 類全名

    ???? * @param url URL? 類路徑

    ???? * @throws FileNotFoundException

    ???? * @throws IOException

    ???? */

    ??? public testClassLoader(String name, URL url) throws

    ??????????? FileNotFoundException, IOException {

    ??????? super(getSystemClassLoader());

    ??????? this.name = name + ".class";

    ?

    ??????? // 打開 URL 指定的資源

    ??????? URLConnection con = url.openConnection();

    ??????? InputStream classIs = con.getInputStream();

    ??????? this.classFile = classIs;

    ??????? ByteArrayOutputStream baos = new ByteArrayOutputStream();

    ??????? byte buf[] = new byte[1024];

    ??????? // 讀取文件流

    ??????? for (int i = 0; (i = classIs.read(buf)) != -1; ) {

    ??????????? baos.write(buf, 0, i);

    ???? ???}

    ??????? classIs.close();

    ??????? baos.close();

    ?

    ??????? // 創(chuàng)建新的類對象

    ??????? byte[] data = baos.toByteArray();

    ??????? defineClass(name, data, 0, data.length);

    ??? }

    ?

    ??? /**

    ???? * 重載 getResourceAsStream() 是為了返回該類的文件流。

    ???? * @return an InputStream of the class bytes, or null

    ???? */

    ??? public InputStream getResourceAsStream(String resourceName) {

    ??????? try {

    ??????????? if (resourceName.equals(name)) {

    ??????????????? return this.classFile;

    ??????????? }

    ??????? } catch (Exception e) {

    ??????????? return null;

    ??????? }

    ??????? return null;

    ??? }

    }

    ?????? 相信大家已經(jīng)明白了吧。我在 ClassLoader 構(gòu)造時便載入了指定的類文件,因此,就跳過了在 new 新的對象時去查找 Cache 中該類是否有載入的過程。(注明一下:我的類路徑用了 URL 的方式傳入,目的是為了適應(yīng)多平臺的機制,因為 Window Linux 的路徑命名不一樣)

    相信聰明的你一定猜到后面應(yīng)該怎么做了吧,沒錯,就是用 Class.forName 構(gòu)建類:

    String name = "test.testBlank";

    ?????? URL url = new URL("file:/c:/test/classes/test/testBlank.class");

    ClassLoader cl = new testClassLoader(name, url);

    Class c = Class.forName(name, false, cl);

    // 實例化

    Object obj = c.newInstance();

    testInterface i = (testInterface) obj;

    i.run();

    ?

    ?????? 你一定注意到了,為什么我用了一個 testInterface ,其實自定義 ClassLoader 中,不能直接以當(dāng)前的類去直接強制轉(zhuǎn)換,因為在 JVM 中,不同的 ClassLoader 載入同一個類,在 JVM 中是兩個完全不同的類。因此,默認(rèn)的 class 文件都是 SystemClassLoader ,所以會拋出 ClassCastException 。解決方法很簡單,只要該對象實現(xiàn)一個接口,然后用父類去強制轉(zhuǎn)換就沒有問題了。

    ?

    ?????? 以上只是我在學(xué)習(xí)過程中的一點積累,如果有什么錯誤,歡迎大家評論。

    ?

    posted on 2006-04-25 11:25 大牙 閱讀(703) 評論(0)  編輯  收藏 所屬分類: 架構(gòu)師歷程
    主站蜘蛛池模板: 久久一区二区三区免费播放| 亚洲日日做天天做日日谢| 亚洲精品无码专区2| 日本视频免费在线| 四虎影院在线免费播放| 国产香蕉九九久久精品免费| 无码区日韩特区永久免费系列 | 亚洲国产无线乱码在线观看 | 亚洲AV永久精品爱情岛论坛| 丁香五月亚洲综合深深爱| 亚洲一区二区女搞男| 亚洲自偷自偷在线制服| 好看的亚洲黄色经典| 久久亚洲精品AB无码播放| 亚洲欧洲第一a在线观看| 亚洲高清视频免费| 亚洲一卡2卡3卡4卡国产网站| 亚洲av产在线精品亚洲第一站| 国产午夜亚洲精品| 在线观看亚洲网站| av网站免费线看| 久久免费公开视频| 黄色网址免费大全| 日韩精品视频免费在线观看| 免费大香伊蕉在人线国产| 亚洲日本一区二区一本一道 | 免费看少妇作爱视频| 免费一级一片一毛片| 激情综合色五月丁香六月亚洲| 亚洲AV永久青草无码精品| 亚洲性无码av在线| 亚洲精品无码中文久久字幕| 一级毛片a免费播放王色电影| 久久国产乱子精品免费女| 日本h在线精品免费观看| 全免费a级毛片免费看不卡| 免费人成无码大片在线观看| 亚洲中文字幕无码一区| 亚洲毛片无码专区亚洲乱| 久久精品国产亚洲av天美18| 久青草视频在线观看免费|