天天看點

打破雙親委派機制有什麼用_被打破的雙親委托機制

在介紹ClassLoader之前,先提幾個問題:

1、建立一個java工程,建立一個Long類,在裡面寫上如下代碼

2、運作,會發生什麼呢?

一、常見的ClassLoader介紹

classLoader 其實就是類加載器,具體作用就是将類加載到java虛拟機中去,之後類中的程式就可以運作了。java中常見的類加載器主要有以下三個:1、BootstrapClassLoader(根類加載器) : BootstrapClassLoader是純C++實作的類加載器,沒有對應的java類,主要加載的是jre/lib/目錄下的核心庫2、ExtClassLoader(擴充類加載器) : 類的全名sun.misc.Launcher

3、AppClassLoader(應用類加載器) : 類的全名sun.misc.LauncherExtClassLoader,主要加載的是jre/lib/ext目錄下的擴充包<br>∗∗∗3、AppClassLoader(應用類加載器):∗∗∗類的全名sun.misc.LauncherAppClassLoader,主要加載CLASSPATH路徑下的包

二、下面通過程式,列印出上面3個類加載器的加載路徑

1、AppClassLoader(應用類加載器)
public class Main {

    public static void main(String[] args) {
        Class mainClass = Main.class;
            ClassLoader classLoader = mainClass.getClassLoader();//列印類加載器名稱
            System.out.print(classLoader.toString());//列印AppClassLoader加載路徑
            URL[] urLs = ((URLClassLoader) classLoader).getURLs();for (URL url: urLs) {
                System.out.print(url);
                System.out.print("\n");
            }
    }
}
列印結果
[email protected]
file:/D:/Study_Space/ClassLoaderDemo/ClassLoaderDemo/bin/
           

從列印結果可看到,AppClassLoader主要加載CLASSPATH路徑下類,還有一些第三方包等,也就是加載我們應用程式的類和第三方庫

2、ExtClassLoader(擴充類加載器)

提到ExtClassLoader,這裡必須注意一下兩個概念的差別:父類加載器和類中繼承,父類加載器不像繼承,它是沒有父子關系的,看一張ClassLoader類的繼承關系圖

打破雙親委派機制有什麼用_被打破的雙親委托機制

在這裡插入圖檔描述

可以看到,AppClassLoader的父類是URLClassLoader,但是AppClassLoader的父類加載器是ExtClassLoader,看下面代碼輸出結果

public class Main {


    public static void main(String[] args) {

        Class mainClass = Main.class;
            ClassLoader classLoader = mainClass.getClassLoader();//擷取AppClassLoader的父類加載器
            ClassLoader extClassLoader = classLoader.getParent();//列印類加載器名稱
            System.out.print( extClassLoader.toString());
            System.out.print("\n");
            URL[] extUrLs = ((URLClassLoader) extClassLoader).getURLs();//列印AppClassLoader加載路徑for (URL url: extUrLs) {
                System.out.print(url);
                System.out.print("\n");
            }
    }
}
列印結果
[email protected]
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/access-bridge-64.jar
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/cldrdata.jar
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/dnsns.jar
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/jaccess.jar
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/jfxrt.jar
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/localedata.jar
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/nashorn.jar
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/sunec.jar
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/sunjce_provider.jar
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/sunmscapi.jar
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/sunpkcs11.jar
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/zipfs.jar
           

可以看出,AppClassLoader的父類加載器是ExtClassLoader,而ExtClassLoader主要加載的是jre/lib/ext目錄下的擴充包。

3、BootstrapClassLoader(根類加載器)

也稱之為啟動類加載器,看到這裡,是不是已經想到BootstrapClassLoader是ExtClassLoader的父類加載器呢。答案是錯誤的,因為BootstrapClassLoader是用C++實作的,他沒有對應的java類,是以BootstrapClassLoader不是ExtClassLoader的父類加載器。而且因為使用C++實作的,是以想要擷取它的加載路徑,就必須用特殊的手段去擷取:通過看AppClassLoader和ExtClassLoader源碼(後面會有源碼解析)我們知道,他們都是Launcher的内部類,而Launcher提供了一個方法(getBootstrapClassPath),來擷取BootstrapClassLoader的加載路徑,是以用什麼手段來擷取BootstrapClassLoader的加載路徑,就呼之欲出了。

可以看出,BootstrapClassLoader負責加載JDK中的核心類庫,如:rt.jar、resources.jar、charsets.jar等。

上面就是java中三個類加載器的加載路徑,下面将會講解一下類加載器是怎麼加載一個類的

二、類加載器是怎麼加載一個類的(父委托加載機制)

類加載器主要通過loadClass這個方法去加載一個類的,下面看源碼

通過上面源碼我們可以知道,如果當類加載器是AppClassLoader時,會判斷它的父加載器parent是否為空,如果不為空,則調用父加載器也就是ExtClassLoader的loadClass(name, false)方法,這個時候又會判斷一次ExtClassLoader的父加載器是否為空,如果不為空,則調用ExtClassLoader的父加載器loadClass方法,當然ExtClassLoader父加載器是為空的,是以,最終會調用BootstrapClassLoader類加載器去加載一個類,這一套加載機制,叫做 父委托加載機制

來一張圖,友善了解 

打破雙親委派機制有什麼用_被打破的雙親委托機制

三、父委托加載機制的好處

現在咱們回頭看看最開始那個問題,你會發現回報一個錯誤

現在應該明白,為什麼會報一個這樣的錯誤了把。就是因為父委托類加載機制。那父委托加載機制有什麼好處呢?

這樣可以提高軟體系統的安全性,防止JVM核心類被覆寫。如果,别人自己寫了一個Long類,在Long類裡面添加一些惡意代碼,是以很有可能會有人利用這個漏洞去攻擊你的程式。同時,他也可以防止類被重複加載。

四、如何自定義類加載器

自定義類加載器分為兩步:

  • 繼承 java.lang.ClassLoader
  • 重寫父類的 findClass 方法。

    可能這裡會有疑問,為什麼隻重寫findClass方法?不是說隻能重寫findClass 方法,你也可以重寫 loadClass 方法,但是重寫loadClass 就得寫 ClassLoader 搜尋類的邏輯,當在 loadClass 中找不到相關類, loadClass 就會調用 findClass 來搜尋類。這個邏輯ClassLoader 已經實作了,是以一般情況下沒必要再去重寫。隻要重寫一個 findClass 就夠了。

    自定義類加載的原因:

  • 代碼安全:比如加密編譯後的位元組碼檔案,然後通過自定義類加載器去加載。這樣就可以減少被反編譯的危險。
  • 動态加載:比如根據需求,動态的從網絡或本地加載指定的位元組碼檔案。

    五、被打破的父委托機制

    在實際的運用中,父委托機制 很好的解決了 基礎類 統一加載的問題,如上所述的 Long 類,這是我們在調用jdk中的一些基礎類;但是有的時候,jdk中的基礎類需要調用我們使用者的代碼,那該怎麼辦?典型的例子就是JNDI服務。

JNDI服務,它的代碼由 啟動類加載器(BootstrapClassLoader) 去加載(在JDK1.3時放進rt.jar),但JNDI的目的就是對資源進行集中管理和查找,它需要調用獨立廠商實作部部署在應用程式的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代碼,但啟動類加載器主要加載的是jre/lib/ 目錄下的核心庫,無法加載這些獨立廠商所編寫的代碼,該怎麼辦?

為了解決上面的問題,java團隊設計了 線程上下檔案類加載器(Thread Context ClassLoader)

1、線程上下檔案類加載器(Thread Context ClassLoader)
  • 這個類加載器可以通過java.lang.Thread類的setContextClassLoader()方法進行設定,如果建立線程時還未設定,它将會從父線程中繼承一個;
  • 如果在應用程式的全局範圍内都沒有設定過,那麼這個類加載器預設就是應用程式類加載器。

有了線程上下文類加載器,JNDI服務使用這個線程上下文類加載器去加載所需要的SPI代碼,也就是父類加載器請求子類加載器去完成類加載動作,這種行為實際上就是打通了雙親委派模型的層次結構來逆向使用類加載器,已經違背了雙親委派模型,但這也是無可奈何的事情。

Java中所有涉及SPI的加載動作基本上都采用這種方式,例如JNDI,JDBC,JCE,JAXB和JBI等。

打破雙親委派機制有什麼用_被打破的雙親委托機制

在這裡插入圖檔描述