溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

深入理解JVM,類加載器

發(fā)布時(shí)間:2020-06-12 00:23:29 來(lái)源:網(wǎng)絡(luò) 閱讀:367 作者:Java筆記丶 欄目:編程語(yǔ)言

虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)把類加載階段中的“通過(guò)一個(gè)類的全限定名來(lái)獲取描述此類的二進(jìn)制字節(jié)流(即字節(jié)碼)”這個(gè)動(dòng)作放到Java虛擬機(jī)外部去實(shí)現(xiàn),以便讓應(yīng)用程序自己決定如何去獲取所需要的類。實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊稱為“類加載器”

一般來(lái)說(shuō),Java 虛擬機(jī)使用 Java 類的方式如下:

  1. Java 源程序(.java 文件)在經(jīng)過(guò) Java 編譯器編譯之后就被轉(zhuǎn)換成字節(jié)碼(.class 文件)。

  2. 類加載器負(fù)責(zé)讀取 Java 字節(jié)代碼,并轉(zhuǎn)換成?java.lang.Class類的一個(gè)實(shí)例。每個(gè)這樣的實(shí)例用來(lái)表示一個(gè) Java 類。通過(guò)此實(shí)例的?newInstance()方法就可以創(chuàng)建出該類的一個(gè)對(duì)象。

實(shí)際的情況可能更加復(fù)雜,比如 Java 字節(jié)代碼可能是通過(guò)工具動(dòng)態(tài)生成的,也可能是通過(guò)網(wǎng)絡(luò)下載的。更詳細(xì)的內(nèi)容可以參考上一篇文章中講類加載過(guò)程中的加載階段時(shí)介紹的幾個(gè)例子(JAR包、Applet、動(dòng)態(tài)代理、JSP等)。

類與類加載器

類加載器雖然只用于實(shí)現(xiàn)類的加載動(dòng)作,但它在Java程序起到的作用卻遠(yuǎn)大于類加載階段。對(duì)于任意一個(gè)類,都需要由加載它的類加載器和這個(gè)類本身一同確立其在Java虛擬機(jī)中的唯一性,每一個(gè)類加載器,都擁有一個(gè)獨(dú)立的類名稱空間。通俗而言:比較兩個(gè)類是否“相等”(這里所指的“相等”,包括類的Class對(duì)象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結(jié)果,也包括使用instanceof()關(guān)鍵字對(duì)做對(duì)象所屬關(guān)系判定等情況),只有在這兩個(gè)類時(shí)由同一個(gè)類加載器加載的前提下才有意義,否則,即使這兩個(gè)類來(lái)源于同一個(gè)Class文件,被同一個(gè)虛擬機(jī)加載,只要加載它們的類加載器不同,那這兩個(gè)類就必定不相等。

雙親委派模型

從jvm的角度來(lái)講,只存在以下兩種不同的類加載器:

  • 啟動(dòng)類加載器(Bootstrap ClassLoader),這個(gè)類加載器用C++實(shí)現(xiàn),是虛擬機(jī)自身的一部分;

  • 所有其他類的加載器,這些類由Java實(shí)現(xiàn),獨(dú)立于虛擬機(jī)外部,并且全都繼承自抽象類java.lang.ClassLoader

從Java開(kāi)發(fā)人員的角度看,類加載器可以劃分得更細(xì)致一些:

  • 啟動(dòng)類加載器(Bootstrap ClassLoader)?此類加載器負(fù)責(zé)將存放在?<JAVA_HOME>\lib?目錄中的,或者被 -Xbootclasspath 參數(shù)所指定的路徑中的,并且是虛擬機(jī)識(shí)別的(僅按照文件名識(shí)別,如 rt.jar,名字不符合的類庫(kù)即使放在lib 目錄中也不會(huì)被加載)類庫(kù)加載到虛擬機(jī)內(nèi)存中。

  • 啟動(dòng)類加載器無(wú)法被 Java 程序直接引用,用戶在編寫自定義類加載器時(shí),如果需要把加載請(qǐng)求委派給引導(dǎo)類加載器,直接使用null代替即可。

  • 擴(kuò)展類加載器(Extension ClassLoader)?這個(gè)類加載器是由ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實(shí)現(xiàn)的。它負(fù)責(zé)將<Java_Home>/lib/ext或者被?java.ext.dir系統(tǒng)變量所指定路徑中的所有類庫(kù)加載到內(nèi)存中,開(kāi)發(fā)者可以直接使用擴(kuò)展類加載器。

  • 應(yīng)用程序類加載器(Application ClassLoader)?這個(gè)類加載器是由?AppClassLoader(sun.misc.Launcher$AppClassLoader)實(shí)現(xiàn)的。由于這個(gè)類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,因此一般稱為系統(tǒng)類加載器。

  • 它負(fù)責(zé)加載用戶類路徑(ClassPath)上所指定的類庫(kù),開(kāi)發(fā)者可以直接使用這個(gè)類加載器,如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器。

由開(kāi)發(fā)人員開(kāi)發(fā)的應(yīng)用程序都是由這三種類加載器相互配合進(jìn)行加載的,如果有必要,還可以加入自己定義的類加載器。這些類加載器的關(guān)系一般如下圖所示:

深入理解JVM,類加載器

上圖展示的類加載器之間的層次關(guān)系,稱為類加載器的雙親委派模型(Parents Delegation Model)。該模型要求除了頂層的啟動(dòng)類加載器外,其余的類加載器都應(yīng)有自己的父類加載器,這里類加載器之間的父子關(guān)系一般通過(guò)組合(Composition)關(guān)系來(lái)實(shí)現(xiàn),而不是通過(guò)繼承(Inheritance)的關(guān)系實(shí)現(xiàn)。

工作過(guò)程

如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載,而是把這個(gè)請(qǐng)求委派給父類加載器,每一個(gè)層次的加載器都是如此,依次遞歸,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o(wú)法完成此加載請(qǐng)求(它搜索范圍中沒(méi)有找到所需類)時(shí),子加載器才會(huì)嘗試自己加載。

優(yōu)點(diǎn)

使用雙親委派模型來(lái)組織類加載器之間的關(guān)系,使得Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系。例如類java.lang.Object,它存放再rt.jar中,無(wú)論哪個(gè)類加載器要加載這個(gè)類,最終都是委派給處于模型最頂端的啟動(dòng)類加載器進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個(gè)類。

相反,如果沒(méi)有雙親委派模型,由各個(gè)類加載器自行加載的話,如果用戶編寫了一個(gè)稱為`java.lang.Object的類,并放在程序的ClassPath中,那系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的Object類,程序?qū)⒆兊靡黄靵y。如果開(kāi)發(fā)者嘗試編寫一個(gè)與rt.jar類庫(kù)中已有類重名的Java類,將會(huì)發(fā)現(xiàn)可以正常編譯,但是永遠(yuǎn)無(wú)法被加載運(yùn)行。

雙親委派模型的實(shí)現(xiàn)如下:

protected?synchronized?Class<?>?loadClass(String?name,boolean?resolve)throws?ClassNotFoundException{
????//check?the?class?has?been?loaded?or?not????Class?c?=?findLoadedClass(name);
????if(c?==?null){
????????try{
????????????if(parent?!=?null){
????????????????c?=?parent.loadClass(name,false);
????????????}else{
????????????????c?=?findBootstrapClassOrNull(name);
????????????}
????????}catch(ClassNotFoundException?e){
????????????//if?throws?the?exception?,the?father?can?not?complete?the?load????????}
????????if(c?==?null){
????????????c?=?findClass(name);
????????}
????}
????if(resolve){
????????resolveClass(c);
????}
????return?c;}

破壞雙親委派模型

線程上下文類加載器

雙親委派模型并不能解決 Java 應(yīng)用開(kāi)發(fā)中會(huì)遇到的類加載器的全部問(wèn)題。Java 提供了很多服務(wù)提供者接口(Service Provider Interface,SPI),允許第三方為這些接口提供實(shí)現(xiàn)。常見(jiàn)的 SPI 有?JDBC、JCE、JNDI、JAXP 和 JBI?等。這些?SPI 的接口由 Java 核心庫(kù)來(lái)提供,如 JAXP 的 SPI 接口定義包含在?javax.xml.parsers包中。

這些 SPI 的實(shí)現(xiàn)代碼很可能是作為 Java 應(yīng)用所依賴的?jar 包被包含進(jìn)來(lái),可以通過(guò)類路徑(ClassPath)來(lái)找到,如實(shí)現(xiàn)了 JAXP SPI 的 Apache Xerces所包含的 jar 包。SPI 接口中的代碼經(jīng)常需要加載具體的實(shí)現(xiàn)類。如 JAXP 中的?javax.xml.parsers.DocumentBuilderFactory類中的?newInstance()?方法用來(lái)生成一個(gè)新的?DocumentBuilderFactory?的實(shí)例。

這里的實(shí)例的真正的類是繼承自?javax.xml.parsers.DocumentBuilderFactory,由 SPI 的實(shí)現(xiàn)所提供的。如在 Apache Xerces 中,實(shí)現(xiàn)的類是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。

而問(wèn)題在于,SPI 的接口Java 核心庫(kù)的一部分,是由引導(dǎo)類加載器加載的,而SPI 實(shí)現(xiàn)的 Java 類一般是由系統(tǒng)類加載器加載的。引導(dǎo)類加載器是無(wú)法找到 SPI 的實(shí)現(xiàn)類的,因?yàn)樗患虞d Java 的核心庫(kù)。

它也不能委派給系統(tǒng)類加載器,因?yàn)樗窍到y(tǒng)類加載器的祖先類加載器。也就是說(shuō),類加載器的雙親委派模型無(wú)法解決這個(gè)問(wèn)題。

為了解決這個(gè)問(wèn)題,Java設(shè)計(jì)團(tuán)隊(duì)只好引入了一個(gè)不太優(yōu)雅的設(shè)計(jì):線程上下文類加載器(Thread Context ClassLoader)

線程上下文類加載器是從 JDK 1.2 開(kāi)始引入的。類?java.lang.Thread中的方法?getContextClassLoader()和?setContextClassLoader(ClassLoader cl)用來(lái)獲取和設(shè)置線程的上下文類加載器。

如果沒(méi)有通過(guò)?setContextClassLoader(ClassLoader cl)方法進(jìn)行設(shè)置的話,線程將繼承其父線程的上下文類加載器。Java 應(yīng)用運(yùn)行的初始線程的上下文類加載器是應(yīng)用程序類加載器。在線程中運(yùn)行的代碼可以通過(guò)此類加載器來(lái)加載類和資源。

有了線程上下文類加載器,就可以做一些“舞弊”的事情了,JNDI服務(wù)使用這個(gè)線程上下文類加載器去加載所需要的SPI代碼,也就是父類加載器請(qǐng)求子類加載器去完成類加載器的動(dòng)作,這種行為實(shí)際上就是打通了雙親委派模型的層次結(jié)構(gòu)來(lái)逆向使用類加載器,已經(jīng)違背了雙親委派模型的一般性原則。

追求程序動(dòng)態(tài)性

這里所說(shuō)的“動(dòng)態(tài)性”指的是當(dāng)前一些非常熱門的名詞:代碼熱替換(HotSwap)、模塊熱部署(Hot Deployment)等。即希望應(yīng)用程序能像計(jì)算機(jī)的外設(shè)一樣,接上鼠標(biāo)、鍵盤,不用重啟就能立即使用,鼠標(biāo)出了問(wèn)題或需要升級(jí)就換個(gè)鼠標(biāo),不用停機(jī)或重啟。

當(dāng)前業(yè)界“事實(shí)上”的Java模塊化標(biāo)準(zhǔn)是OSGi,而OSGi實(shí)現(xiàn)代碼熱部署的關(guān)鍵則是它自定義的類機(jī)載器的實(shí)現(xiàn)。關(guān)于OSGi的細(xì)節(jié)將在稍后的案例分析中詳細(xì)講解。

自定義類加載器

API


深入理解JVM,類加載器


其中有如下三個(gè)比較重要的方法

深入理解JVM,類加載器


在了解完上述內(nèi)容后,我們可以容易地意識(shí)到自定義類加載器有以下兩種方式:

  • 采用雙親委派模型:繼承ClassLoader類,只需重寫其的findClass(String name)方法,而不需重寫loadClass(String name)方法。

  • 破壞雙親委派模型:繼承ClassLoader類,需要整個(gè)重寫實(shí)現(xiàn)了雙親委派模型邏輯的loadClass(String name)方法。

實(shí)例

下面我們來(lái)實(shí)現(xiàn)一個(gè)自定義類加載器,用來(lái)加載存儲(chǔ)在文件系統(tǒng)上的 Java 字節(jié)代碼。

public?class?FileSystemClassLoader?extends?ClassLoader?{?
?
???private?String?rootDir;?
?
???public?FileSystemClassLoader(String?rootDir)?{?
???????this.rootDir?=?rootDir;?
???}?
?
???@Override
???protected?Class<?>?findClass(String?name)?throws?ClassNotFoundException?{?
???????byte[]?classData?=?getClassData(name);?
???????if?(classData?==?null)?{?
???????????throw?new?ClassNotFoundException();?
???????}?
???????else?{?
???????????return?defineClass(name,?classData,?0,?classData.length);?
???????}?
???}?
?
???private?byte[]?getClassData(String?className)?{?
???????String?path?=?classNameToPath(className);?
???????try?{?
???????????InputStream?ins?=?new?FileInputStream(path);?
???????????ByteArrayOutputStream?baos?=?new?ByteArrayOutputStream();?
???????????int?bufferSize?=?4096;?
???????????byte[]?buffer?=?new?byte[bufferSize];?
???????????int?bytesNumRead?=?0;?
???????????while?((bytesNumRead?=?ins.read(buffer))?!=?-1)?{?
???????????????baos.write(buffer,?0,?bytesNumRead);?
???????????}?
???????????return?baos.toByteArray();?
???????}?catch?(IOException?e)?{?
???????????e.printStackTrace();?
???????}?
???????return?null;?
???}?
?
???private?String?classNameToPath(String?className)?{?
???????return?rootDir?+?File.separatorChar?
???????????????+?className.replace('.',?File.separatorChar)?+?".class";?
???}?}

類 FileSystemClassLoader的?findClass()方法首先根據(jù)類的全名在硬盤上查找類的字節(jié)代碼文件(.class 文件),然后讀取該文件內(nèi)容,最后通過(guò) defineClass()方法來(lái)把這些字節(jié)代碼轉(zhuǎn)換成?java.lang.Class類的實(shí)例。

案例分析

Tomcat:正統(tǒng)的類加載器架構(gòu)

主流的Java Web服務(wù)器如Tomcat、Jetty、WebLogic、WebSphere等等,都實(shí)現(xiàn)了自己定義的類加載器(一般都不止一個(gè))。因?yàn)橐粋€(gè)功能健全的Web服務(wù)器,要解決以下問(wèn)題:

  • 部署在同一個(gè)服務(wù)器上的兩個(gè)Web應(yīng)用程序所使用的Java類庫(kù)可以實(shí)現(xiàn)相互隔離。?兩個(gè)不同的應(yīng)用程序可能會(huì)依賴同一個(gè)第三方類庫(kù)的不同版本,不能要求一個(gè)類庫(kù)在一個(gè)服務(wù)器中只有一份,服務(wù)器應(yīng)當(dāng)保證兩個(gè)應(yīng)用程序的類庫(kù)可以互相獨(dú)立使用。

  • 部署在同一個(gè)服務(wù)器上的兩個(gè)Web應(yīng)用程序所使用的Java類庫(kù)可以相互共享。?例如,用戶可能有5個(gè)使用Spring組織的應(yīng)用程序部署在同一臺(tái)服務(wù)器上,如果把5份Spring分別放在各個(gè)應(yīng)用程序的隔離目錄中,庫(kù)在使用時(shí)都要被加載到服務(wù)器內(nèi)存中,JVM的方法區(qū)就會(huì)有過(guò)度膨脹的風(fēng)險(xiǎn)。

  • 服務(wù)器需要盡可能保證自身安全不受部署的Web應(yīng)用程序影響。?很多Web服務(wù)器本身是用Java實(shí)現(xiàn)的,服務(wù)器使用的類庫(kù)應(yīng)該與應(yīng)用程序的類庫(kù)相互獨(dú)立。

  • 支持JSP應(yīng)用的服務(wù)器,大多數(shù)需要支持代碼熱替換(HotSwap)功能。?JSP文件由于其純文本存儲(chǔ)的特性,運(yùn)行時(shí)修改的概率遠(yuǎn)大于第三方類庫(kù)或程序自身的Class文件,因此需要做到修改后無(wú)須重啟。

鑒于上述問(wèn)題,各種Web服務(wù)器都不約而同地提供了數(shù)個(gè)ClassPath路徑供用戶存放第三方類庫(kù),這些路徑一般以“l(fā)ib”或“classes”命名。以Tomcat為例,有3組目錄(“/common/* ”、“/server/* ”和“/shared/* ”)可以存放Java類庫(kù),另外還可以加上Web應(yīng)用程序自身的目錄“/WEB-INF/* ”,一共4組,把Java類庫(kù)放置在這些目錄中的含義分別如下:

  • /common目錄:類庫(kù)可被Tomcat和所有的Web應(yīng)用程序共同使用。

  • /server目錄:類庫(kù)可被Tomcat使用,對(duì)所有的Web應(yīng)用程序都不可見(jiàn)。

  • /shared目錄:類庫(kù)可被所有的Web應(yīng)用程序共同使用,但對(duì)Tomcat自己不可見(jiàn)。

  • /WebApp/WEB-INF目錄:類庫(kù)僅僅可以被此Web應(yīng)用程序使用,對(duì)Tomcat和其他Web應(yīng)用程序都不可見(jiàn)。

為了支持這套目錄結(jié)構(gòu),并對(duì)目錄里的類庫(kù)進(jìn)行加載和隔離,Tomcat采用如下經(jīng)典的雙親委派模型來(lái)實(shí)現(xiàn)了多個(gè)類加載器:

深入理解JVM,類加載器

CommonClassLoader、CatalinaClassLoaderSharedClassLoaderWebappClassLoader是Tomcat自己定義的類加載器,它們分別加載/common/* 、/server/*、/shared/**和/WebApp/WEB-INF/*中的Java類庫(kù)。其中WebApp類加載器和JSP類加載器通常會(huì)存在多個(gè)實(shí)例,每一個(gè)Web應(yīng)用程序?qū)?yīng)一個(gè)WebApp類加載器,每一個(gè)JSP文件對(duì)應(yīng)一個(gè)JSP類加載器。

CommonClassLoader能加載的類都可以被CatalinaClassLoaderSharedClassLoader使用,而CatalinaClassLoaderSharedClassLoader自己能加載的類則與對(duì)方相互隔離。WebAppClassLoader可以使用SharedClassLoader加載到的類,但各個(gè)WebAppClassLoader實(shí)例之間相互隔離。而JasperLoader的加載范圍僅是這個(gè)JSP文件編譯出來(lái)的那一個(gè)Class,它出現(xiàn)的目的就是被丟棄。當(dāng)服務(wù)器檢測(cè)到JSP文件被修改時(shí),會(huì)替換掉目前的JasperLoader的實(shí)例,并通過(guò)再建立一個(gè)新的JSP類加載器來(lái)實(shí)現(xiàn)JSP文件的HotSwap功能。

特殊場(chǎng)景

前文提到過(guò)一個(gè)場(chǎng)景,如果有5個(gè)Web應(yīng)用程序都是用Spring來(lái)進(jìn)行組織和管理的話,可以把Spring放到CommonShared目錄下讓這些程序共享。Spring要對(duì)用戶程序的類進(jìn)行管理,自然要能訪問(wèn)到用戶程序的類,而用戶程序放在/WebApp/WEB-INF目錄中,這時(shí)就需要破壞雙親委派模型,使用線程上下文類加載器來(lái)完成這一工作了。

OSGi:類加載器的靈活運(yùn)用

OSGi(Open Service Gateway Initiative)是OSGi聯(lián)盟制定的一個(gè)基于Java語(yǔ)言的動(dòng)態(tài)模塊化規(guī)范,現(xiàn)在成為了Java“事實(shí)上”的模塊化標(biāo)準(zhǔn)。它為開(kāi)發(fā)人員提供了面向服務(wù)和基于組件的運(yùn)行環(huán)境,并提供標(biāo)準(zhǔn)的方式用來(lái)管理軟件的生命周期。OSGi 已經(jīng)被實(shí)現(xiàn)和部署在很多產(chǎn)品上,在開(kāi)源社區(qū)也得到了廣泛的支持,其中最為著名的應(yīng)用莫過(guò)于大家都很熟悉的Eclipse IDE。

OSGi 中的每個(gè)模塊(bundle)都包含?Java Package和Class。模塊可以聲明它所依賴的需要導(dǎo)入(import)的其它模塊的 Java 包和類(通過(guò)?Import-Package),也可以聲明導(dǎo)出(export)自己的包和類,供其它模塊使用(通過(guò)?Export-Package)。也就是說(shuō)需要能夠隱藏和共享一個(gè)模塊中的某些 Java 包和類。這是通過(guò) OSGi 特有的類加載器機(jī)制來(lái)實(shí)現(xiàn)的。

OSGi 中的每個(gè)模塊都有對(duì)應(yīng)的一個(gè)類加載器,它負(fù)責(zé)加載模塊自己包含的 Java 包和類。當(dāng)它需要加載 Java 核心庫(kù)的類時(shí)(以 java開(kāi)頭的包和類),它會(huì)代理給父類加載器(通常是啟動(dòng)類加載器)來(lái)完成。當(dāng)它需要加載所導(dǎo)入的 Java 類時(shí),它會(huì)代理給導(dǎo)出此 Java 類的模塊來(lái)完成加載。模塊也可以顯式的聲明某些 Java 包和類,必須由父類加載器來(lái)加載。只需要設(shè)置系統(tǒng)屬性?org.osgi.framework.bootdelegation的值即可。

假設(shè)有兩個(gè)模塊 bundleA 和 bundleB,它們都有自己對(duì)應(yīng)的類加載器 ClassLoaderA 和 ClassLoaderB。在 bundleA 中包含類 com.bundleA.Sample,并且該類被聲明為導(dǎo)出的,也就是說(shuō)可以被其它模塊所使用的。

bundleB 聲明了導(dǎo)入 bundleA 提供的類?com.bundleA.Sample,并包含一個(gè)類?com.bundleB.NewSample繼承自?com.bundleA.Sample。在 bundleB 啟動(dòng)的時(shí)候,其類加載器 classLoaderB 需要加載類?com.bundleB.NewSample,進(jìn)而需要加載類?com.bundleA.Sample。

由于 bundleB 聲明了類?com.bundleA.Sample是導(dǎo)入的,classLoaderB 把加載類?com.bundleA.Sample的工作代理給導(dǎo)出該類的 bundleA 的類加載器 ClassLoaderA。ClassLoaderA 在其模塊內(nèi)部查找類?com.bundleA.Sample并定義它,所得到的類?com.bundleA.Sample實(shí)例就可以被所有聲明導(dǎo)入了此類的模塊使用。

對(duì)于以 java開(kāi)頭的類,都是由父類加載器來(lái)加載的。

如果聲明了系統(tǒng)屬性?org.osgi.framework.bootdelegation=com.example.core.*,那么對(duì)于包?com.example.core中的類,都是由父類加載器來(lái)完成的。

OSGi 模塊的這種類加載器結(jié)構(gòu),使得一個(gè)類的不同版本可以共存在 Java 虛擬機(jī)中,帶來(lái)了很大的靈活性。不過(guò)它的這種不同,也會(huì)給開(kāi)發(fā)人員帶來(lái)一些麻煩,尤其當(dāng)模塊需要使用第三方提供的庫(kù)的時(shí)候。下面提供幾條比較好的建議:

  • 如果一個(gè)類庫(kù)只有一個(gè)模塊使用,把該類庫(kù)的 jar 包放在模塊中,在 Bundle-ClassPath中指明即可。

  • 如果一個(gè)類庫(kù)被多個(gè)模塊共用,可以為這個(gè)類庫(kù)單獨(dú)的創(chuàng)建一個(gè)模塊,把其它模塊需要用到的 Java 包聲明為導(dǎo)出的。其它模塊聲明導(dǎo)入這些類。

  • 如果類庫(kù)提供了 SPI 接口,并且利用線程上下文類加載器來(lái)加載 SPI 實(shí)現(xiàn)的 Java 類,有可能會(huì)找不到 Java 類。如果出現(xiàn)了 NoClassDefFoundError異常,首先檢查當(dāng)前線程的上下文類加載器是否正確。通過(guò)?Thread.currentThread().getContextClassLoader()就可以得到該類加載器。該類加載器應(yīng)該是該模塊對(duì)應(yīng)的類加載器。如果不是的話,可以首先通過(guò)?class.getClassLoader()來(lái)得到模塊對(duì)應(yīng)的類加載器,再通過(guò)?Thread.currentThread().setContextClassLoader()來(lái)設(shè)置當(dāng)前線程的上下文類加載器。


向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

jvm j
AI