??ClassLoader在Java中有著非常重要的作用骄恶,它主要工作在Class裝載的加載階段食铐,其主要作用是從系統(tǒng)外部獲得Class二進(jìn)制數(shù)據(jù)流。ClassLoader是Java的核心組件僧鲁,所有的Class都是由ClassLoader進(jìn)行加載的虐呻,ClassLoader負(fù)責(zé)通過(guò)各種方式將Class信息的二進(jìn)制數(shù)據(jù)流讀入系統(tǒng)象泵,然后交給Java虛擬機(jī)進(jìn)行連接、初始化等操作斟叼。因此偶惠,ClassLoader在整個(gè)裝載階段,只能影響到類(lèi)的加載朗涩,而無(wú)法通過(guò)ClassLoader去改變類(lèi)的連接和初始化行為忽孽。
??從代碼層次看,ClassLoader是一個(gè)抽象類(lèi)谢床,它提供了一些重要的接口兄一,用于自定義Class的加載流程和加載方式。ClassLoader的主要方法如下:
public Class<?> loadClass(String name) throws ClassNotFoundException
給定一個(gè)類(lèi)名萤悴,加載一個(gè)類(lèi)瘾腰,返回代表這個(gè)類(lèi)的Class實(shí)例,如果找不到類(lèi)覆履,則返回ClassNotFoundException異常protected final Class<?> defineClass(byte[] b,int off,int len)
根據(jù)給定的字節(jié)碼流b定義一個(gè)類(lèi),off和len參數(shù)表示實(shí)際Class信息在byte數(shù)組中的位置和長(zhǎng)度费薄,其中byte數(shù)組b是ClassLoader從外部獲取的硝全。這是受保護(hù)的方法,只有在自定義ClassLoader子類(lèi)中可以使用protected Class<?> findClass(String name) throws ClassNotFoundException
查找一個(gè)類(lèi)楞抡,這是一個(gè)受保護(hù)的方法扇商,也是重載ClassLoader時(shí)界牡,重要的系統(tǒng)擴(kuò)展點(diǎn)。這個(gè)方法會(huì)在loadClass()時(shí)被調(diào)用,用于自定義查找類(lèi)的邏輯菲驴。如果不需要修改類(lèi)加載默認(rèn)機(jī)制,只是想改變類(lèi)加載的形式就可以重載該方法protected final Class<?> findLoadedClass(String name)
這也是一個(gè)受保護(hù)的方法汤纸,它會(huì)去尋找已經(jīng)加載的類(lèi)织狐。這個(gè)方法是final方法,無(wú)法被修改
??在ClassLoader的結(jié)構(gòu)中筹煮,還有一個(gè)重要的字段parent遮精,它也是一個(gè)ClassLoader的實(shí)例,這個(gè)字段所表示的ClassLoader也稱(chēng)為這個(gè)ClassLoader的雙親败潦。在類(lèi)加載的過(guò)程中本冲,ClassLoader可能會(huì)將某些請(qǐng)求交予自己的雙親處理。
ClassLoader的分類(lèi)
&nbap;?在標(biāo)準(zhǔn)的Java程序中劫扒,Java虛擬機(jī)會(huì)創(chuàng)建3類(lèi)ClassLoader為整個(gè)應(yīng)用程序服務(wù)檬洞。它們分別是:BootStrapClassLoader(啟動(dòng)類(lèi)加載器)、ExtensionClassLoader(擴(kuò)展類(lèi)加載器)和AppClassLoader(應(yīng)用類(lèi)加載器沟饥,也稱(chēng)為系統(tǒng)類(lèi)加載器)添怔。此外环戈,每個(gè)應(yīng)用程序還可以擁有自定義的ClassLoader,擴(kuò)展Java虛擬機(jī)獲取Class數(shù)據(jù)的能力澎灸。
??各個(gè)ClassLoader的層次和功能如下圖所示院塞,從ClassLoader的層次自頂往下為啟動(dòng)類(lèi)加載器、擴(kuò)展類(lèi)加載器性昭、應(yīng)用類(lèi)加載器和自定義類(lèi)加載器拦止。其中,應(yīng)用類(lèi)加載器的雙親為擴(kuò)展類(lèi)加載器糜颠,擴(kuò)展類(lèi)加載器的雙親為啟動(dòng)類(lèi)加載器汹族。當(dāng)系統(tǒng)需要使用一個(gè)類(lèi)時(shí),在判斷類(lèi)是否已經(jīng)被加載時(shí)其兴,會(huì)先從當(dāng)前底層類(lèi)加載器進(jìn)行判斷顶瞒。當(dāng)系統(tǒng)需要加載一個(gè)類(lèi)時(shí),會(huì)從頂層類(lèi)開(kāi)始加載元旬,依次向下嘗試榴徐,直到成功。
??在這些ClassLoader中匀归,啟動(dòng)類(lèi)加載器最為特別坑资,它是完全由C代碼實(shí)現(xiàn)的,并且在Java中沒(méi)有對(duì)象與之對(duì)應(yīng)穆端。系統(tǒng)的核心類(lèi)就是由啟動(dòng)類(lèi)加載器進(jìn)行加載的袱贮,它也是虛擬機(jī)的核心組件。擴(kuò)展類(lèi)加載器和應(yīng)用類(lèi)加載器都有對(duì)應(yīng)的Java對(duì)象可供使用体啰。
??無(wú)法在Java代碼中直接訪問(wèn)啟動(dòng)類(lèi)加載器攒巍,因?yàn)檫@是一個(gè)純C實(shí)現(xiàn),因此任何加載在啟動(dòng)類(lèi)加載器中的類(lèi)是無(wú)法獲得其ClassLoader實(shí)例的荒勇,比如:
String.class.getClassLoader()
由于String屬于Java核心類(lèi)柒莉,由啟動(dòng)類(lèi)加載器加載,故以上代碼返回的是null枕屉。
ClassLoader的雙親委托模式
??系統(tǒng)中的ClassLoader在協(xié)同工作時(shí)常柄,默認(rèn)會(huì)使用雙親委托模式。即在類(lèi)加載的時(shí)候搀擂,系統(tǒng)會(huì)判斷當(dāng)前類(lèi)是否已經(jīng)被加載西潘,如果已經(jīng)被加載,就會(huì)直接返回可用的類(lèi)哨颂,否則就會(huì)嘗試加載喷市,在嘗試加載時(shí),會(huì)先請(qǐng)求雙親處理威恼,如果雙親請(qǐng)求失敗品姓,則會(huì)自己加載寝并。
注意:雙親為null有兩種情況:第一,其雙親就是啟動(dòng)類(lèi)加載器腹备;第二衬潦,當(dāng)前加載器就是啟動(dòng)類(lèi)加載器。判斷類(lèi)是否加載時(shí)植酥,應(yīng)用類(lèi)加載器會(huì)順著雙親路徑往上判斷镀岛,直到啟動(dòng)類(lèi)加載器。但是啟動(dòng)類(lèi)加載器不會(huì)往下詢(xún)問(wèn)友驮,這個(gè)委托是單向的漂羊。
雙親委托模式的弊端
??前文提到,檢查類(lèi)是否加載的委托過(guò)程是單向的卸留,這個(gè)方式雖然從結(jié)構(gòu)上說(shuō)比較清晰走越,使各個(gè)ClassLoader的職責(zé)非常明確,但是同時(shí)會(huì)帶來(lái)一個(gè)問(wèn)題耻瑟,即頂層的ClassLoader無(wú)法訪問(wèn)底層的ClassLoader所加載的類(lèi)旨指。
??通常情況下,啟動(dòng)類(lèi)加載器中的類(lèi)為系統(tǒng)核心類(lèi)匆赃,包括一些重要的系統(tǒng)接口淤毛,而在應(yīng)用類(lèi)加載器中,為應(yīng)用類(lèi)算柳。按照這種模式,應(yīng)用類(lèi)訪問(wèn)系統(tǒng)類(lèi)自然是沒(méi)有問(wèn)題姓言,但是系統(tǒng)類(lèi)訪問(wèn)應(yīng)用類(lèi)就會(huì)出現(xiàn)問(wèn)題瞬项。比如在系統(tǒng)類(lèi)中提供了一個(gè)接口,該接口需要在應(yīng)用類(lèi)中得以實(shí)現(xiàn)何荚,該接口還綁定一個(gè)工廠方法囱淋,用于創(chuàng)建該接口的實(shí)例,而接口和工廠方法都在啟動(dòng)類(lèi)加載器中餐塘。這時(shí)妥衣,就會(huì)出現(xiàn)該工廠方法無(wú)法創(chuàng)建由應(yīng)用類(lèi)加載器加載的應(yīng)用實(shí)例的問(wèn)題。
雙親委托模式的補(bǔ)充
??在Java平臺(tái)中戒傻,把核心類(lèi)(rt.jar)中提供外部服務(wù)税手,可由應(yīng)用層自行實(shí)現(xiàn)的接口,通承枘桑可以稱(chēng)為Service Provider Interface芦倒,即SPI。
??為了解決啟動(dòng)類(lèi)加載器無(wú)法訪問(wèn)應(yīng)用類(lèi)加載器加載的類(lèi)的問(wèn)題不翩,Java中通過(guò)把一個(gè)ClassLoader置于一個(gè)線程實(shí)例中兵扬,這個(gè)ClassLoader叫做上下文加載器麻裳,使該ClassLoader成為一個(gè)相對(duì)共享的實(shí)例。默認(rèn)情況下器钟,上下文加載器就是應(yīng)用類(lèi)加載器津坑,這樣即使是在啟動(dòng)類(lèi)加載器中的代碼也可以通過(guò)這種方式訪問(wèn)應(yīng)用類(lèi)加載器的類(lèi),其示意圖如下所示:
突破雙親模式
??雙親模式的類(lèi)加載方式是虛擬機(jī)默認(rèn)的行為傲霸,但并非必須這么做疆瑰,通過(guò)重載ClassLoader可以修改該行為。事實(shí)上狞谱,不少應(yīng)用軟件和框架都修改了這種行為乃摹,比如Tomcat和OSGi框架,都有各自獨(dú)特的類(lèi)加載順序跟衅。具體的過(guò)程就不嗷述了孵睬,感興趣的可以取查閱資料。
熱替換的實(shí)現(xiàn)
??熱替換是指在程序的運(yùn)行過(guò)程中伶跷,不停止服務(wù)掰读,只通過(guò)替換程序文件來(lái)修改程序的行為。熱替換的關(guān)鍵需求在于服務(wù)不能中斷叭莫,修改必須立即表現(xiàn)正在運(yùn)行的系統(tǒng)之中蹈集。基本上大部分腳本語(yǔ)言都是天生支持熱替換的雇初,比圖PHP拢肆,只要替換了PHP源文件,這種改動(dòng)就會(huì)立即生效靖诗,而無(wú)需重啟Web服務(wù)器郭怪。
??但對(duì)Java來(lái)說(shuō),熱替換并非天生就支持刊橘,如果一個(gè)類(lèi)已經(jīng)加載到系統(tǒng)中鄙才,通過(guò)修改類(lèi)文件,并無(wú)法讓系統(tǒng)再來(lái)加載并重定義這個(gè)類(lèi)促绵。因此攒庵,在Java中實(shí)現(xiàn)這一功能的一個(gè)可行的方法就是靈活運(yùn)用ClassLoader。
注意:由不同ClassLoader加載的同名類(lèi)屬于不同的類(lèi)型败晴,不能相互轉(zhuǎn)換和兼容浓冒。即兩個(gè)不同的ClassLoader加載同一個(gè)類(lèi),在虛擬機(jī)內(nèi)部位衩,會(huì)認(rèn)為這2個(gè)類(lèi)是完全不同的裆蒸。
??根據(jù)這個(gè)特點(diǎn),可以用來(lái)模擬熱替換的實(shí)現(xiàn)糖驴,基本思路如下圖所示: