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

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

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

    牙牙窩

    BlogJava 聯系 聚合 管理
      8 Posts :: 21 Stories :: 10 Comments :: 0 Trackbacks

    JAVA 類動態載入的實現

    1 ?????? 前言

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

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

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

    JVM Java 虛擬機)啟動時,會形成由三個類加載器組成的初始類加載器層次結構:

    ??????? bootstrap classloader
    ???????????????? |
    ??????? extension classloader
    ???????????????? |
    ??????? system classloader

    bootstrap classloader
    引導(也稱為原始)類加載器,它負責加載 Java 的核心類。在 Sun JVM 中,在執行 java 的命令中使用 -Xbootclasspath 選項或使用 -D 選項指定 sun.boot.class.path 系統屬性值可以指定附加的類。這個加載器的是非常特殊的,它實際上不是 java.lang.ClassLoader 的子類,而是由 JVM 自身實現的。大家可以通過執行以下代碼來獲得 bootstrap classloader 加載了那些核心類庫:
    ??? URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
    ??? for (int i = 0; i < urls.length; i++) {
    ????? System.out.println(urls
    .toExternalForm());
    ??? }
    在我的計算機上的結果為:
    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
    這時大家知道了為什么我們不需要在系統屬性 CLASSPATH 中指定這些類庫了吧,因為 JVM 在啟動的時候就自動加載它們了。

    extension classloader
    擴展類加載器,它負責加載 JRE 的擴展目錄( JAVA_HOME/jre/lib/ext 或者由 java.ext.dirs 系統屬性指定的)中 JAR 的類包。這為引入除 Java 核心類以外的新功能提供了一個標準機制。因為默認的擴展目錄對所有從同一個 JRE 中啟動的 JVM 都是通用的,所以放入這個目錄的 JAR 類包對所有的 JVM system classloader 都是可見的。在這個實例上調用方法 getParent() 總是返回空值 null ,因為引導加載器 bootstrap classloader 不是一個真正的 ClassLoader 實例。所以當大家執行以下代碼時:
    ??? System.out.println(System.getProperty("java.ext.dirs"));
    ??? ClassLoader extensionClassloader=ClassLoader.getSystemClassLoader().getParent();
    ??? System.out.println("the parent of extension classloader : "+extensionClassloader.getParent());
    結果為:
    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
    系統(也稱為應用)類加載器,它負責在 JVM 被啟動時,加載來自在命令 java 中的 -classpath 或者 java.class.path 系統屬性或者 CLASSPATH 操作系統屬性所指定的 JAR 類包和類路徑??偰芡ㄟ^靜態方法 ClassLoader.getSystemClassLoader() 找到該類加載器。如果沒有特別指定,則用戶自定義的任何類加載器都將該類加載器作為它的父加載器。執行以下代碼即可獲得:
    ??? System.out.println(System.getProperty("java.class.path"));
    輸出結果則為用戶在系統屬性里面設置的 CLASSPATH
    classloader
    加載類用的是全盤負責委托機制。所謂全盤負責,即是當一個 classloader 加載一個 Class 的時候,這個 Class 所依賴的和引用的所有 Class 也由這個 classloader 負責載入,除非是顯式的使用另外一個 classloader 載入;委托機制則是先讓 parent (父)類加載器 ( 而不是 super ,它與 parent classloader 類不是繼承關系 ) 尋找,只有在 parent 找不到的時候才從自己的類路徑中去尋找。此外類加載還采用了 cache 機制,也就是如果 cache 中保存了這個 Class 就直接返回它,如果沒有才從文件中讀取和轉換成 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 相關的類路徑中尋找)。如果找不到則到
    7.
    6.
    從文件中載入 Class ,到
    8.
    7.
    拋出
    ClassNotFoundException.
    8.
    返回
    Class.

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


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

    下面就讓我們來看看 JVM 是如何來為我們來建立類加載器的結構的:
    sun.misc.Launcher
    ,顧名思義,當你執行 java 命令的時候, JVM 會先使用 bootstrap classloader 載入并初始化一個 Launcher ,執行下來代碼:
    ?? System.out.println("the Launcher's classloader is "+sun.misc.Launcher.getLauncher().getClass().getClassLoader());
    結果為:
    ?? the Launcher's classloader is null (
    因為是用 bootstrap classloader 加載 , 所以 class loader null)
    Launcher
    會根據系統和命令設定初始化好 class loader 結構, JVM 就用它來獲得 extension classloader system classloader, 并載入所有的需要載入的 Class ,最后執行 java 命令指定的帶有靜態的 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 設置成當前線程的 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() {
    ??????? //
    獲得系統屬性 “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
    ??? {
    ??????? //
    獲得系統屬性 “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 是使用系統屬性 “java.ext.dirs” 設置類搜索路徑的,并且沒有 parent 。 system classloader 是使用系統屬性 “java.class.path” 設置類搜索路徑的,并且有一個 parent classloader Launcher 初始化 extension classloader , system classloader ,并將 system classloader 設置成為 context classloader ,但是僅僅返回 system classloader JVM 。


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

    ???
    好了,現在我們了解了 classloader 的結構和工作原理,那么我們如何實現在運行時的動態載入和更新呢?只要我們能夠動態改變類搜索路徑和清除 classloader cache 中已經載入的 Class 就行了,有兩個方案,一是我們繼承一個 classloader ,覆蓋 loadclass 方法,動態的尋找 Class 文件并使用 defineClass 方法來;另一個則非常簡單實用,只要重新使用一個新的類搜索路徑來 new 一個 classloader 就行了,這樣即更新了類搜索路徑以便來載入新的 Class ,也重新生成了一個空白的 cache( 當然 , 類搜索路徑不一定必須更改 ) 。

    (結束)

    ?

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

    ?

    3 ?????? 實現類的動態載入

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

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

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

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

    ?

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

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

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

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

    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();

    ?

    ??????? // 創建新的類對象

    ??????? 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;

    ??? }

    }

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

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

    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 中,不能直接以當前的類去直接強制轉換,因為在 JVM 中,不同的 ClassLoader 載入同一個類,在 JVM 中是兩個完全不同的類。因此,默認的 class 文件都是 SystemClassLoader ,所以會拋出 ClassCastException 。解決方法很簡單,只要該對象實現一個接口,然后用父類去強制轉換就沒有問題了。

    ?

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

    ?

    posted on 2006-04-25 11:25 大牙 閱讀(703) 評論(0)  編輯  收藏 所屬分類: 架構師歷程
    主站蜘蛛池模板: 日本人成在线视频免费播放| 国产jizzjizz视频全部免费| 青青草国产免费国产是公开 | 亚洲精品天堂无码中文字幕| 国产亚洲免费的视频看| 日本免费人成视频播放 | 亚洲乱码中文字幕手机在线| 波多野结衣免费在线观看| a视频在线免费观看| 色妞www精品视频免费看| 亚洲天堂免费在线| 美女黄频a美女大全免费皮| 亚洲精品国产第1页| 国产亚洲精AA在线观看SEE| 少妇亚洲免费精品| 老司机69精品成免费视频| 色多多A级毛片免费看| 久久久国产亚洲精品| 亚洲中文无码a∨在线观看| 亚洲av无码一区二区三区不卡| 免费又黄又爽又猛的毛片| 日韩免费无码一区二区视频| 久热中文字幕在线精品免费| 黄网站色视频免费在线观看的a站最新| 四虎国产精品成人免费久久| 亚洲成AV人片在WWW| 亚洲中文字幕一区精品自拍| 亚洲精品成人网站在线播放| 亚洲av永久无码制服河南实里| 精品国产亚洲一区二区在线观看| 国产一级做a爱免费视频| 国产免费人人看大香伊| 国产99视频精品免费视频7| 性做久久久久免费观看| 日韩精品视频免费网址| 日韩成全视频观看免费观看高清| 成人人免费夜夜视频观看| 国产99久久久久久免费看| 久久久久免费视频| 在线观看免费无码视频| 久操免费在线观看|