Java虛擬機之自定義類加載器

創建用戶自定義的類加載器

  要創建用戶自定義的類加載器,只需要擴展java.lang.ClassLoader類,然後覆蓋它的findClass(String name)方法即可,該方法根據參數指定的類的名字,返回對應的Class對象的引用。

自定義類加載器的例子

  代碼:

複製代碼
package com.mengdd.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader {

    private String name; // 類加載器的名字
    private String path = "d:\\"; // 加載類的路徑
    private final String fileType = ".class"; // class文件的擴展名

    public MyClassLoader(String name) {
        super(); // 讓系統類加載器成爲該類加載器的父加載器
        this.name = name;
    }

    public MyClassLoader(ClassLoader parent, String name) {
        super(parent); // 顯式指定該類加載器的父加載器
        this.name = name;
    }

    @Override
    public String toString() {
        return this.name;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        // 重寫的時候把protected改爲public

        // 獲取字節數組
        byte[] data = this.loadClassData(name);
        // 將字節數組轉換成Class對象返回
        return this.defineClass(name, data, 0, data.length);

    }

    /**
     * 得到class文件的二進制字節數組
     *
     * @param name
     * @return
     */
    private byte[] loadClassData(String name) {

        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;

        try {

            // 將完整類名中的.轉化成\
            name = name.replace(".", "\\");
            is = new FileInputStream(new File(path + name + fileType));

            baos = new ByteArrayOutputStream();
            int ch = 0;
            while (-1 != (ch = is.read())) {

                baos.write(ch);
            }

            data = baos.toByteArray();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            try {
                is.close();
                baos.close();
            }
            catch (Exception e2) {
            }
        }
        return data;
    }

    // main方法用來測試
    public static void main(String[] args) throws Exception {

        MyClassLoader loader1 = new MyClassLoader("loader1");
        // loader1的父加載器是系統類加載器
        // 系統類加載器會在classpath指定的目錄中加載類
        loader1.setPath("d:\\myapp\\serverlib\\");

        MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
        // 將loader1作爲loader2的父加載器
        loader2.setPath("d:\\myapp\\clientlib\\");

        MyClassLoader loader3 = new MyClassLoader(null, "loader3");
        // loader3的父加載器是根類加載器
        loader3.setPath("d:\\myapp\\otherlib\\");

        // 測試加載
        test(loader2);
        test(loader3);
        System.out.println("test2---------------");
        // 測試不同命名空間的類的互相訪問
        test2(loader3);
    }

    public static void test(ClassLoader loader) throws Exception {
        Class clazz = loader.loadClass("com.mengdd.classloader.Sample");

        Object object = clazz.newInstance();

    }

    public static void test2(ClassLoader loader) throws Exception {
        Class clazz = loader.loadClass("com.mengdd.classloader.Sample");

        Sample object = (Sample) clazz.newInstance();

        System.out.println("sample v1: " + object.v1);

    }
}
複製代碼

  其中Sample:

複製代碼
package com.mengdd.classloader;

public class Sample {

    public int v1 = 1;

    public Sample() {
        System.out.println("Sample is loaded by: "
                + this.getClass().getClassLoader());

        // 主動使用Dog類
        new Dog();
    }
}
複製代碼

  Dog類:

複製代碼
package com.mengdd.classloader;

public class Dog {

    public Dog() {
        System.out.println("Dog is loaded by: "
                + this.getClass().getClassLoader());
    }
}
複製代碼

  例子演示過程略,嘗試把class文件放在不同的路徑下,看輸出或者報錯結果。

  主要結論就是驗證了父親委託機制。

  採用loader1的時候由於其父類是系統類加載器(也即應用類加載器),所以如果可以在classpath中找到目標.class文件,則定義類加載器是系統類加載器,輸出類似:

  sun.misc.Launcher$AppClassLoader@7448bc3d

  每個類加載器都有自己的命名空間,命名空間由該加載器及所有父加載器所加載的類組成。

 

  在Sample類中主動使用了Dog類,當執行Sample類的構造方法中的new Dog()語句時,Java虛擬機需要先加載Dog類,到底用哪個類加載器加載呢?

  從打印結果可以看出,Java虛擬機會用Sample類的定義類加載器去加載Dog類,加載過程也同樣採用父親委託機制

  如果Sample類首次主動使用Dog時,Sample類的加載器及它的父加載器都無法加載Dog類,將會拋出找不到文件的異常。

不同類加載器的命名空間關係

  同一個命名空間內的類是相互可見的,即可以互相訪問。

  子加載器的命名空間包含所有父加載器的命名空間。

  因此由子加載器加載的類能看見父加載器加載的類。

  例如系統類加載器加載的類能看見根類加載器加載的類。

  由父加載器加載的類不能看見子加載器加載的類。

  可以理解爲:由於子加載器中含有父加載器的引用,所以子加載器的範圍更大

  如果兩個加載器之間沒有直接或間接的父子關係,那麼它們各自加載的類相互不可見。

  比如這麼一種情況:MyClassLoader類由系統類加載器加載,而Sample類由loader3類加載器加載,因此MyClassLoader類看不見Sample類。

  在MyClassLoader類的main()方法中使用Sample類,會導致錯誤。

  當兩個不同命名空間內的類互相不可見時,可採用Java反射機制來訪問對方實例的屬性和方法,即反射可以突破命名空間的限制。

 

參考資料

  聖思園張龍老師Java SE視頻教程。

  ClassLoader類:http://docs.oracle.com/javase/7/docs/api/

  相關博文:

  Java虛擬機JVM學習05 類加載器的父委託機制:http://www.cnblogs.com/mengdd/p/3562540.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章