打破双亲委派机制有什么用_被打破的双亲委托机制
在介紹ClassLoader之前,先提幾個問題:
1、新建一個java工程,創建一個Long類,在里面寫上如下代碼
public?class?Long?{
????public?static?void?main(String[]?args)?{
????????System.out.print("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");
????????????}
????}
}
打印結果
sun.misc.Launcher$AppClassLoader@4e0e2f2a
file:/D:/Study_Space/ClassLoaderDemo/ClassLoaderDemo/bin/
從打印結果可看到,AppClassLoader主要加載CLASSPATH路徑下類,還有一些第三方包等,也就是加載我們應用程序的類和第三方庫
2、ExtClassLoader(擴展類加載器)
提到ExtClassLoader,這里必須注意一下兩個概念的區別:父類加載器和類中繼承,父類加載器不像繼承,它是沒有父子關系的,看一張ClassLoader類的繼承關系圖
在這里插入圖片描述
可以看到,AppClassLoader的父類是URLClassLoader,但是AppClassLoader的父類加載器是ExtClassLoader,看下面代碼輸出結果
????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");
????????????}
????}
}
打印結果
sun.misc.Launcher$ExtClassLoader@2a139a55
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的加載路徑,就呼之欲出了。
public?class?Main?{????public?static?void?main(String[]?args)?{
????????????try?{
????????????????Class?launcherClass?=?Class.forName("sun.misc.Launcher");
????????????????Method?methodGetBootstrapClassPath=?launcherClass.getDeclaredMethod("getBootstrapClassPath",?null);
????????????????if(methodGetBootstrapClassPath!=null){
????????????????????methodGetBootstrapClassPath.setAccessible(true);
????????????????????Object?obj?=?methodGetBootstrapClassPath.invoke(null,?null);
????????????????????if(obj!=null){
????????????????????????Method?methodGetUrls?=??obj.getClass().getDeclaredMethod("getURLs",?null);
????????????????????????if(methodGetUrls?!=?null){
????????????????????????????methodGetUrls.setAccessible(true);
?????????????????????????????URL[]?bootstrapClassPath?=?(URL[])?methodGetUrls.invoke(obj,?null);
?????????????????????????????for?(URL?url:?bootstrapClassPath)?{
????????????????????????????????????System.out.print(url);
????????????????????????????????????System.out.print("\n");
????????????????????????????????}
????????????????????????}
????????????????????}
????????????????}
????????????}?catch?(ClassNotFoundException?|?NoSuchMethodException?|?SecurityException?|?IllegalAccessException?|?IllegalArgumentException?|?InvocationTargetException?e)?{
????????????????e.printStackTrace();
????????????}
????}
}
打印結果
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/resources.jar
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/rt.jar
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/sunrsasign.jar
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/jsse.jar
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/jce.jar
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/charsets.jar
file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/jfr.jar
...
可以看出,BootstrapClassLoader負責加載JDK中的核心類庫,如:rt.jar、resources.jar、charsets.jar等。
上面就是java中三個類加載器的加載路徑,下面將會講解一下類加載器是怎么加載一個類的
二、類加載器是怎么加載一個類的(父委托加載機制)
類加載器主要通過loadClass這個方法去加載一個類的,下面看源碼
public?Class>?loadClass(String?name)?throws?ClassNotFoundException?{????????return?loadClass(name,?false);
????}
protected?Class>?loadClass(String?name,?boolean?resolve)?throws?ClassNotFoundException?{
????????synchronized?(getClassLoadingLock(name))?{
????????????//?First,?check?if?the?class?has?already?been?loaded?判斷這個類是否被加載過,如果被加載過,直接返回
????????????Class>?c?=?findLoadedClass(name);
????????????if?(c?==?null)?{//沒有被加載過
????????????????long?t0?=?System.nanoTime();
????????????????try?{
????????????????????if?(parent?!=?null)?{//判斷類加載器的父類加載器是否為空
????????????????????????c?=?parent.loadClass(name,?false);//調用父類加載器的loadClass
????????????????????}?else?{//父類加載器為空,就調用findBootstrapClass方法
????????????????????????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
????????????????????//?to?find?the?class.
????????????????????long?t1?=?System.nanoTime();
????????????????????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;
????????}
????}
????private?Class>?findBootstrapClassOrNull(String?name)?{
????????if?(!checkName(name))?return?null;
????????return?findBootstrapClass(name);
????}
????//?return?null?if?not?found
????private?native?Class>?findBootstrapClass(String?name);
通過上面源碼我們可以知道,如果當類加載器是AppClassLoader時,會判斷它的父加載器parent是否為空,如果不為空,則調用父加載器也就是ExtClassLoader的loadClass(name, false)方法,這個時候又會判斷一次ExtClassLoader的父加載器是否為空,如果不為空,則調用ExtClassLoader的父加載器loadClass方法,當然ExtClassLoader父加載器是為空的,所以,最終會調用BootstrapClassLoader類加載器去加載一個類,這一套加載機制,叫做 父委托加載機制
來一張圖,方便理解?
三、父委托加載機制的好處
現在咱們回頭看看最開始那個問題,你會發現回報一個錯誤
錯誤:?在類?java.lang.Long?中找不到?main?方法,?請將?main?方法定義為:???public?static?void?main(String[]?args)
否則?JavaFX?應用程序類必須擴展javafx.application.Application
現在應該明白,為什么會報一個這樣的錯誤了把。就是因為父委托類加載機制。那父委托加載機制有什么好處呢?
這樣可以提高軟件系統的安全性,防止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等。
參考文章:https://www.sohu.com/a/334000357_505800
https://blog.51cto.com/14888355/2515924
在這里插入圖片描述
總結
以上是生活随笔為你收集整理的打破双亲委派机制有什么用_被打破的双亲委托机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [云炬学英语]每日一句2020.8.26
- 下一篇: [云炬学英语]每日一句2020.8.28