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物件的邏輯 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(); } } }