在《深入理解Java類加載機(jī)制,再也不用死記硬背了》這篇文章中提到,,從JVM的角度看,,加載的讀取二進(jìn)制流和初始化階段,是開放了主導(dǎo)權(quán)給用戶的,。而剩下的所有部分都是JVM內(nèi)部完成的,。 那為什么要這樣做呢?這是符合面向?qū)ο笾械拈_閉原則和封裝思想的設(shè)計,。JVM將類加載內(nèi)部復(fù)雜的實(shí)現(xiàn)封裝了起來,,拒絕開發(fā)者修改。只提供了一個拓展接口,,用于二進(jìn)制流的讀取,。 流程上搞懂了,那JVM是怎樣使用代碼來實(shí)現(xiàn)這些步驟的呢,?這就要聊到Java的類加載器了,。 類加載器 類加載器的分類是Java規(guī)范,屬于抽象的概念,。規(guī)范將類加載器分成啟動類和非啟動類兩大類,。 HotSpot對類加載器實(shí)現(xiàn)有以下分類(以下描述中省略了HotSpot定語): 啟動類加載器是C/C++語言實(shí)現(xiàn)的,無法作為對象被程序引用,。主要用來加載Java的核心類庫,。類庫主要由Java啟動參數(shù)指定,默認(rèn)是${JAVA_HOME}/lib,。HotSpot還會對類名進(jìn)行白名單校驗(yàn)來提高安全性,。 非啟動類加載器都是使用Java來實(shí)現(xiàn),繼承自java.lang.ClassLoader,,可以作為類對象被程序引用,。它也分成3種,簡單說一下區(qū)別,。 擴(kuò)展類加載器ExtensionClassLoader加載的是${JAVA_HOME}/lib/ext下或者啟動時指定的類庫,。它目標(biāo)是加載Java類庫的擴(kuò)展,是標(biāo)準(zhǔn)類庫的補(bǔ)充,。 應(yīng)用類加載器ApplicationClassLoader加載的是環(huán)境變量classpath指定的類庫,。咱們平時使用maven的話,就是使用maven加載的類庫和自己編寫的代碼,。 用戶類加載器就是用戶自己定義的類加載器,也叫自定義類加載器,。只要繼承于應(yīng)用類加載器即可,。定義不同的類加載器會使用不同的命名空間,。因?yàn)椴煌拿臻g是個隱含的限定名區(qū)分,是不同的對象,。 雙親委派模型 雙親委派的目標(biāo)是在默認(rèn)情況下,,一個限定名的類只會被一個類加載器加載并解析使用,這樣在程序中,,它就是唯一的,,不會產(chǎn)生歧義。 在被動的情況下,,當(dāng)一個類加載器收到類加載請求,,它不會首先自己去加載。而是傳遞給父加載器,。這樣,,所有的類首先都會先由最上層的啟動類加載器進(jìn)行加載,只有父加載器無法完成類加載才會由子加載器完成,。 白話來說:雙親委派模型中,,如果類A調(diào)用了類B,那類B可能是類A在使用過程中被動加載進(jìn)來的,。那如果類A是用應(yīng)用程序類加載器加載的,,那么類B只能由應(yīng)用程序類加載器或其父類或者上一層父類來加載。不能由自定義類加載器來來加載,。 雙親委派模型并不是一個擁有強(qiáng)約束力的模型,。它存在設(shè)計缺陷,在一些情況下可以被主動破壞,。 五種經(jīng)典的破壞雙親委派場景 第一次破壞 在《深入理解Java虛擬機(jī)》這本書中,,記錄了怎樣破壞雙親委派:因?yàn)殡p親委派機(jī)制原則在java.lang.ClassLoader的loadClass方法中。只要重寫loadClass方法就可以破壞,。書中還寫了一個重寫loadClass方法來進(jìn)行破壞的小樣例,,這個小樣例被稱為雙親委派的第一次破壞。 Tomcat場景 這個破壞的樣例有沒有什么實(shí)際價值意義呢,?還真有,,后來Tomcat就使用這種方式對雙親委派進(jìn)行破壞,來達(dá)到使用一個web容器部署兩個或者多個應(yīng)用程序,,不同的應(yīng)用程序,,可能會依賴同一個第三方類庫的不同版本,還要能保證每一個應(yīng)用程序的類庫都是獨(dú)立,、相互隔離的效果,。 tomcat自定義了類加載器,重寫loadClass方法使其優(yōu)先加載自己目錄下的class文件,來達(dá)到class私有的效果,。不過咱們現(xiàn)在流行使用的都是嵌入式的web容器了,,將來更多的場景還是一個應(yīng)用程序使用一個單獨(dú)的web容器。所以這種破壞雙親委派的價值在降低,。 基于SPI的三種經(jīng)典破壞場景 還有一種java特性叫做SPI,,在《JAVA SPI(Service Provider Interface)原理、設(shè)計及源碼解析》里,,我不僅說了什么是SPI,,還提到了三種經(jīng)典場景都在使用,它們分別是jdbc,、Dubbo,、Eleasticsearch。沒錯,,這三種經(jīng)典場景通過SPI都使得雙親委派遭到破壞,。下面以jdbc為例做說明。 jdbc驅(qū)動Driver在十幾年前,,我還手寫過用DriverManager來加載的代碼,。 Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "1234"); DriverManager初始化時是這樣的,注意紅框內(nèi)容,。 紅框內(nèi)容翻譯一下就是說:由啟動類加載器加載的DriverManager初始化時要去加載Driver驅(qū)動,。 jdbc驅(qū)動由Serverloader.load加載。 而Serverloader.load里優(yōu)先使用當(dāng)前線程的類加載器而不是自身使用的類加載器來加載Driver,。當(dāng)前線程是使用方要么是應(yīng)用類加載器要么是自定義類加載器,,總歸類A調(diào)用類B,B沒有使用父類而使用了子類加載器,,所以破壞了雙親委派,。 雙親委派被破壞的補(bǔ)救措施 那朋友就問了,java.lang.ClassLoader把loadClass方法定義為final是不是就解決了雙親委派被破壞的問題呢,?java.lang.ClassLoader的loadClass方法在Java很早的版本就有了,,而雙親委派模型是在JDK1.2中引入的。Java是向下兼容的,,所以不是不想改而是改不了了,。一個補(bǔ)救措施是推薦使用findClass方法而不是直接重寫loadClass。當(dāng)然了,,如果有人登錄了服務(wù)器,,把JDK文件給替換了,也就失效了,。這個不在討論范圍,。
|
|