1. 程式人生 > >java 詳解類載入器的雙親委派及打破雙親委派

java 詳解類載入器的雙親委派及打破雙親委派

http://www.jb51.net/article/102920.htm

https://www.cnblogs.com/wxd0108/p/6681618.html

其實,雙親委派模型並不複雜。自定義類載入器也不難!隨便從網上搜一下就能搜出一大把結果,然後copy一下就能用。但是,如果每次想自定義類載入器就必須搜一遍別人的文章,然後複製,這樣顯然不行。可是自定義類載入器又不經常用,時間久了容易忘記。相信你經常會記不太清loadClassfindClassdefineClass這些函式我到底應該重寫哪一個?它們主要是做什麼的?本文大致分析了各個函式的流程,目的就是讓你看完之後,難以忘記!或者說,延長你對自定義類載入器的記憶時間!隨時隨地想自定義就自定義!

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。

舉個簡單例子:

ClassLoader1ClassLoader2都載入java.lang.String類,對應Class1、Class2物件。那麼Class1物件不屬於ClassLoad2物件載入的java.lang.String型別。

二、打破雙親委派機制則不僅要繼承ClassLoader類,還要重寫loadClass和findClass方法,如下例子:

①定義Test類。

?
12345public class Test {public Test(){System.out.println(this.getClass().getClassLoader().toString());}}

②重新定義一個繼承ClassLoader的TestClassLoaderN類,這個類與前面的TestClassLoader類很相似,但它除了重寫findClass方法外還重寫了loadClass方法,預設的loadClass方法是實現了雙親委派機制的邏輯,即會先讓父類載入器載入,當無法載入時才由自己載入。這裡為了破壞雙親委派機制必須重寫loadClass方法,即這裡先嚐試交由System類載入器載入,載入失敗才會由自己載入。它並沒有優先交給父類載入器,這就打破了雙親委派機制。

?
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768public class TestClassLoaderN extends ClassLoader {private String name;public TestClassLoaderN(ClassLoader parent, String name) {super(parent);this.name = name;}@Overridepublic String toString() {return this.name;}@Overridepublic 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;}@Overridepublic 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();}}}