java 詳解類載入器的雙親委派及打破雙親委派
http://www.jb51.net/article/102920.htm
https://www.cnblogs.com/wxd0108/p/6681618.html
其實,雙親委派模型並不複雜。自定義類載入器也不難!隨便從網上搜一下就能搜出一大把結果,然後copy
一下就能用。但是,如果每次想自定義類載入器就必須搜一遍別人的文章,然後複製,這樣顯然不行。可是自定義類載入器又不經常用,時間久了容易忘記。相信你經常會記不太清loadClass
、findClass
、defineClass
這些函式我到底應該重寫哪一個?它們主要是做什麼的?本文大致分析了各個函式的流程,目的就是讓你看完之後,難以忘記!或者說,延長你對自定義類載入器的記憶時間!隨時隨地想自定義就自定義!
1. 雙親委派模型
關於雙親委派模型,網上的資料有很多。我這裡只簡單的描述一下,就當是複習。
1.1 什麼是雙親委派模型?
首先,先要知道什麼是類載入器。簡單說,類載入器就是根據指定全限定名稱將class
檔案載入到JVM
記憶體,轉為Class
物件。如果站在JVM
的角度來看,只存在兩種類載入器:
啟動類載入器(
Bootstrap ClassLoader
):由C++
語言實現(針對HotSpot
),負責將存放在<JAVA_HOME>\lib
目錄或-Xbootclasspath
引數指定的路徑中的類庫載入到記憶體中。其他類載入器:由
Java
語言實現,繼承自抽象類ClassLoader
。如:
- 擴充套件類載入器(
Extension ClassLoader
):負責載入<JAVA_HOME>\lib\ext
目錄或java.ext.dirs
系統變數指定的路徑中的所有類庫。- 應用程式類載入器(
Application ClassLoader
)。負責載入使用者類路徑(classpath
)上的指定類庫,我們可以直接使用這個類載入器。一般情況,如果我們沒有自定義類載入器預設就是用這個載入器。
雙親委派模型工作過程是:如果一個類載入器收到類載入的請求,它首先不會自己去嘗試載入這個類,而是把這個請求委派給父類載入器完成。每個類載入器都是如此,只有當父載入器在自己的搜尋範圍內找不到指定的類時(即ClassNotFoundException
),子載入器才會嘗試自己去載入。
類載入器的雙親委派模型
1.2 為什麼需要雙親委派模型?
為什麼需要雙親委派模型呢?假設沒有雙親委派模型,試想一個場景:
黑客自定義一個
java.lang.String
類,該String
類具有系統的String
類一樣的功能,只是在某個函式稍作修改。比如equals
函式,這個函式經常使用,如果在這這個函式中,黑客加入一些“病毒程式碼”。並且通過自定義類載入器加入到JVM
中。此時,如果沒有雙親委派模型,那麼JVM
就可能誤以為黑客自定義的java.lang.String
類是系統的String
類,導致“病毒程式碼”被執行。
而有了雙親委派模型,黑客自定義的java.lang.String
類永遠都不會被載入進記憶體。因為首先是最頂端的類載入器載入系統的java.lang.String
類,最終自定義的類載入器無法載入java.lang.String
類。
或許你會想,我在自定義的類載入器裡面強制載入自定義的java.lang.String
類,不去通過呼叫父載入器不就好了嗎?確實,這樣是可行。但是,在JVM
中,判斷一個物件是否是某個型別時,如果該物件的實際型別與待比較的型別的類載入器不同,那麼會返回false。
舉個簡單例子:
ClassLoader1
、ClassLoader2
都載入java.lang.String
類,對應Class1、Class2物件。那麼Class1
物件不屬於ClassLoad2
物件載入的java.lang.String
型別。
二、打破雙親委派機制則不僅要繼承ClassLoader類,還要重寫loadClass和findClass方法,如下例子:
①定義Test類。
?12345 | public class Test { public Test(){ System.out.println( this .getClass().getClassLoader().toString()); } } |
②重新定義一個繼承ClassLoader的TestClassLoaderN類,這個類與前面的TestClassLoader類很相似,但它除了重寫findClass方法外還重寫了loadClass方法,預設的loadClass方法是實現了雙親委派機制的邏輯,即會先讓父類載入器載入,當無法載入時才由自己載入。這裡為了破壞雙親委派機制必須重寫loadClass方法,即這裡先嚐試交由System類載入器載入,載入失敗才會由自己載入。它並沒有優先交給父類載入器,這就打破了雙親委派機制。
?1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768 | public class TestClassLoaderN extends ClassLoader { private String name; public TestClassLoaderN(ClassLoader parent, String name) { super (parent); this .name = name; } @Override public String toString() { return this .name; } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { Class<?> clazz = null ; ClassLoader system = getSystemClassLoader(); try { clazz = system.loadClass(name); } catch (Exception e) { // ignore } if (clazz != null ) return clazz; clazz = findClass(name); return clazz; } @Override public Class<?> findClass(String name) { InputStream is = null ; byte [] data = null ; ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { is = new FileInputStream( new File( "d:/Test.class" )); int c = 0 ; while (- 1 != (c = is.read())) { baos.write(c); } data = baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } finally { try { is.close(); baos.close(); } catch (IOException e) { e.printStackTrace(); } } return this .defineClass(name, data, 0 , data.length); } public static void main(String[] args) { TestClassLoaderN loader = new TestClassLoaderN( TestClassLoaderN. class .getClassLoader(), "TestLoaderN" ); Class clazz; try { clazz = loader.loadClass( "test.classloader.Test" ); Object object = clazz.newInstance(); } catch (Exception e) { e.printStackTrace(); } } } |