1. 程式人生 > >ClassLoader學習之 自定義classLoader和雙親委派原理

ClassLoader學習之 自定義classLoader和雙親委派原理

ClassLoader學習之  自定義classLoader和雙親委派原理

1、ClassLoader原理介紹

​ ClassLoader使用的是雙親委託模型來搜尋類的,每個ClassLoader例項都有一個父類載入器的引用(不是繼承的關係,是一個包含的關係),虛擬機器內建的類載入器(Bootstrap ClassLoader)本身沒有父類載入器,但可以用作其它ClassLoader例項的的父類載入器。當一個ClassLoader例項需要載入某個類時,它會試圖親自搜尋某個類之前,先把這個任務委託給它的父類載入器,這個過程是由上至下依次檢查的,

2、為什麼要使用雙親委託這種模型呢?

​ 因為這樣可以避免重複載入,當父親已經載入了該類的時候,就沒有必要子ClassLoader再載入一次。考慮到安全因素,我們試想一下,如果不使用這種委託模式,那我們就可以隨時使用自定義的String來動態替代java核心api中定義的型別,這樣會存在非常大的安全隱患,而雙親委託的方式,就可以避免這種情況,因為String已經在啟動時就被引導類載入器(Bootstrcp ClassLoader)載入,所以使用者自定義的ClassLoader永遠也無法載入一個自己寫的String,除非你改變JDK中ClassLoader搜尋類的預設演算法。

3、ClassLoader的體系架構:

類載入器間的關係

我們進一步瞭解類載入器間的關係(並非指繼承關係),主要可以分為以下4點

  • 啟動類載入器,由C++實現,沒有父類。

  • 拓展類載入器(ExtClassLoader),由Java語言實現,父類載入器為null

  • 系統類載入器(AppClassLoader),由Java語言實現,父類載入器為ExtClassLoader

  • ​自定義類載入器,父類載入器肯定為AppClassLoader。

頂層的類載入器是ClassLoader類,它是一個抽象類,其後所有的類載入器都繼承自ClassLoader(不包括啟動類載入器),這裡我們主要介紹ClassLoader中幾個比較重要的方法。

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
  {
      synchronized (getClassLoadingLock(name)) {
          // 先從快取查詢該class物件,找到就不用重新載入
          Class<?> c = findLoadedClass(name);
          if (c == null) {
              long t0 = System.nanoTime
();
             try {                  if (parent != null) {                      //如果找不到,則委託給父類載入器去載入                      c = parent.loadClass(name, false);                 } else {                  //如果沒有父類,則委託給啟動載入器去載入                      c = findBootstrapClassOrNull(name);                 }             } catch (ClassNotFoundException e) {                  // ClassNotFoundException thrown if class not found                  // from the non-null parent class loader             }              if (c == null) {                  // If still not found, then invoke findClass in order                  // 如果都沒有找到,則通過自定義實現的findClass去查詢並載入                  c = findClass(name);                  // this is the defining class loader; record the stats                  sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);                  sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);                  sun.misc.PerfCounter.getFindClasses().increment();             }         }          if (resolve) {//是否需要在載入時進行解析              resolveClass(c);         }          return c;     } }

​ Lancher初始化時首先會建立ExtClassLoader類載入器,然後再建立AppClassLoader並把ExtClassLoader傳遞給它作為父類載入器,這裡還把AppClassLoader預設設定為執行緒上下文類載入器

public Launcher() {
    // 首先建立拓展類載入器
    ClassLoader extcl;
    try {
        extcl = ExtClassLoader.getExtClassLoader();
    } catch (IOException e) {
        throw new InternalError(
            "Could not create extension class loader");
    }

    // Now create the class loader to use to launch the application
    try {
        //再建立AppClassLoader並把extcl作為父載入器傳遞給AppClassLoader
        loader = AppClassLoader.getAppClassLoader(extcl);
    } catch (IOException e) {
        throw new InternalError(
            "Could not create application class loader");
    }

    //設定執行緒上下文類載入器,稍後分析
    Thread.currentThread().setContextClassLoader(loader);
    //省略其他沒必要的程式碼......
}
}

4、建立自定義類載入器,繼承ClassLoader

package com.willow.classloader;

import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * 自定義類載入器
 */
public class FileSystemClassLoader extends ClassLoader {

    private String rootDir;  //指定這個類載入器載入的目錄  d:/study/

    public FileSystemClassLoader(String rootDir) {
        this.rootDir = rootDir;
    }

    //編寫獲取類的位元組碼並建立class物件的邏輯
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> c = findLoadedClass(name);//查詢這個類是否載入了
        if (c != null) {
            return c;
        }
        ClassLoader parent = this.getParent(); //獲取到父類載入器
        try {
            c = parent.loadClass(name); //委派給父類載入
        }catch (ClassNotFoundException e){

        }

        if (c != null) {
            return c;
        } else {
            try {
                byte[] classData=getClassData(name);
                if(classData==null){
                    throw new ClassNotFoundException();
                }else{
                    //方法接受一組位元組,然後將其具體化為一個Class型別例項,它一般從磁碟上載入一個檔案,然後將檔案的位元組傳遞給JVM,通過JVM(native 方法)對於Class的定義,將其具體化,例項化為一個Class型別例項。
                    c=defineClass(name,classData,0,classData.length);
                    return c;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }


        }

        return c;
    }

    /**
     * 編寫讀取位元組流的方法
     *
     * @param className
     * @return
     * @throws IOException
     */
    private byte[] getClassData(String className) throws IOException {  //com.willow.entity.user   轉換為: d:/study/  com/willow/entity/user
        String path = rootDir + "/" + className.replace('.', '/') + ".class";

        InputStream inputStream = null;
        ByteOutputStream outputStream = new ByteOutputStream();
        try {
            inputStream = new FileInputStream(path); //讀取需要載入的類
            byte[] buffer = new byte[1024];
            int temp = 0;
            while ((temp = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, temp);
            }
            return outputStream.toByteArray();


        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }

        return null;
    }
}

測試自定義類載入器

public class MyClassLoderTest {

    public static void main(String[] args) {

        //列印ClassLoader類的層次結構
        ClassLoader classLoader = MyClassLoderTest.class.getClassLoader();    //獲得載入ClassLoaderTest.class這個類的類載入器
        while(classLoader != null) {
            System.out.println(classLoader);
            classLoader = classLoader.getParent();    //獲得父類載入器的引用
        }
        System.out.println(classLoader);


        //測試自定義類載入器  ,在d:/study 建立HelloWorld.java ,然後編譯為HelloWorld.class 檔案
        FileSystemClassLoader loader=new FileSystemClassLoader("d:/study");
        FileSystemClassLoader loader2=new FileSystemClassLoader("d:/study");
        try {
            Class<?> c = loader.loadClass("HelloWorld");
            Class<?> c1 = loader.loadClass("HelloWorld");
            Class<?> c3 = loader2.loadClass("HelloWorld");


            Class<?> cString = loader2.loadClass("java.lang.String");
            Class<?> cMyClassLoderTest = loader2.loadClass("com.willow.classloader.MyClassLoderTest");
            System.out.println(c.hashCode()+"##:classLoader"+c.getClassLoader());    //自定義載入器載入
            System.out.println(c1.hashCode());
            System.out.println(c3.hashCode());  //同一個類,不同的載入器載入,JVM 認為也是不相同的類
            System.out.println(cString.hashCode()+"##:classLoader"+cString.getClassLoader());  //引導類載入器
            System.out.println(cMyClassLoderTest.hashCode()+"##:classLoader"+cMyClassLoderTest.getClassLoader());  //系統預設載入器
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}