日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢(xún)
選擇下列產(chǎn)品馬上在線(xiàn)溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
從Jar包沖突搞到類(lèi)加載機(jī)制,就是這么霸氣

背景

目前市面上項(xiàng)目管理要么是基于Maven,要么是基于Gradle,最近接手了一套純手動(dòng)添加jar包的項(xiàng)目。

對(duì)于純手動(dòng)添加jar包的項(xiàng)目已經(jīng)是多年前的方式了,現(xiàn)在工作三五年的技術(shù)人員可能都沒(méi)有經(jīng)歷過(guò)。就是把項(xiàng)目中所需的jar包挨個(gè)找出來(lái),添加到一個(gè)lib目錄中,在IDE中再將jar包依賴(lài)手動(dòng)添加上。

這種方式來(lái)添加jar包依賴(lài),不僅費(fèi)事,而且很容易出現(xiàn)jar包沖突,同時(shí)分析沖突手段,只能憑借經(jīng)驗(yàn)。

最近就遇到這樣一種情況:一個(gè)項(xiàng)目在開(kāi)發(fā)者A的環(huán)境中可以正常啟動(dòng),在B那里就無(wú)法啟動(dòng),而異常信息是找不到什么什么類(lèi)。

稍微有一些開(kāi)發(fā)經(jīng)驗(yàn)的人,馬上就可以斷定是jar包沖突導(dǎo)致。下面就看看如何解決及引申出來(lái)的知識(shí)點(diǎn)。

臨時(shí)解決方案

由于暫時(shí)無(wú)法對(duì)項(xiàng)目進(jìn)行大范圍重構(gòu),也不敢輕易將Jar包進(jìn)行替換升級(jí)。只能采用臨時(shí)的手段來(lái)進(jìn)行解決。

這里總結(jié)幾個(gè)步驟以備不時(shí)之需,通常也是解決Jar依賴(lài)問(wèn)題的小技巧。

第一:在IDE中查找異常中找不到的類(lèi)。比如IDEA MAC操作系統(tǒng),我用的快捷鍵是command + shift + n。

查找沖突

以Assert類(lèi)為例,可以看到有很多包都包含了Assert,但啟動(dòng)程序卻報(bào)找不到該類(lèi)的某個(gè)方法,問(wèn)題基本上就出在Jar包沖突上了。

第二,定位到Jar包沖突之后,找到系統(tǒng)本應(yīng)該使用的Jar包。

比如這里需要使用的spring-core中的類(lèi),而不spring.jar中的類(lèi)。那么,就可以利用JVM的類(lèi)加載順序機(jī)制,讓JVM先加載spring-core的jar包。

知識(shí)點(diǎn):在同一目錄下的jar包,JVM是按照jar包的先后順序進(jìn)行加載,一旦一個(gè)全路徑名相同的類(lèi)被加載之后,后面再有相同的類(lèi)便不會(huì)進(jìn)行加載了。

因此,臨時(shí)解決方案就是調(diào)整JVM編譯(加載)Jar包的順序。這個(gè)在Eclipse和Idea中都有支持,可以手動(dòng)進(jìn)行調(diào)整。

Eclipse中調(diào)整方式:

Eclipse調(diào)整順序

Idea中調(diào)整方式:

Idea調(diào)整順序

把需要優(yōu)先加載的jar包往上調(diào)整,這樣就可以?xún)?yōu)先加載它,總算是臨時(shí)解決了jar包沖突的問(wèn)題。

類(lèi)加載機(jī)制的延伸

上面只是受限于項(xiàng)目現(xiàn)狀的臨時(shí)解決方案,最終肯定是要進(jìn)行改造升級(jí)的,基于Maven或Gradle進(jìn)行Jar包管理,同時(shí)解決掉Jar包沖突的問(wèn)題的。

在這個(gè)臨時(shí)解決方案,涉及到一個(gè)JVM的關(guān)鍵知識(shí)點(diǎn):JVM的類(lèi)加載器的隔離問(wèn)題及雙親委派機(jī)制。如果沒(méi)有JVM類(lèi)加載機(jī)制的相關(guān)知識(shí),可能連上面的臨時(shí)方案都無(wú)法想到。

類(lèi)加載器的隔離問(wèn)題

每個(gè)類(lèi)裝載器都有一個(gè)自己的命名空間用來(lái)保存已裝載的類(lèi)。當(dāng)一個(gè)類(lèi)裝載器裝載一個(gè)類(lèi)時(shí),它會(huì)通過(guò)保存在命名空間里的類(lèi)全局限定名(Fully Qualified Class Name) 進(jìn)行搜索來(lái)檢測(cè)這個(gè)類(lèi)是否已經(jīng)被加載了。

JVM 對(duì)類(lèi)唯一的識(shí)別是 ClassLoader id + PackageName + ClassName,所以一個(gè)運(yùn)行程序中是有可能存在兩個(gè)包名和類(lèi)名完全一致的類(lèi)的。并且如果這兩個(gè)類(lèi)不是由一個(gè) ClassLoader 加載,是無(wú)法將一個(gè)類(lèi)的實(shí)例強(qiáng)轉(zhuǎn)為另外一個(gè)類(lèi)的,這就是 ClassLoader 隔離性。

為了解決類(lèi)加載器的隔離問(wèn)題,JVM引入了雙親委派機(jī)制。

雙親委派機(jī)制

雙親委派機(jī)制的核心有兩點(diǎn):第一,自底向上檢查類(lèi)是否已加載;其二,自頂向下嘗試加載類(lèi)。

類(lèi)加載器

類(lèi)加載器通常有四類(lèi):?jiǎn)?dòng)類(lèi)加載器、拓展類(lèi)加載器、應(yīng)用程序類(lèi)加載器和自定義類(lèi)加載器。

暫且不考慮自定義類(lèi)加載器,JDK自帶類(lèi)加載器具體執(zhí)行過(guò)程如下:

第一:當(dāng)AppClassLoader加載一個(gè)class時(shí),會(huì)把類(lèi)加載請(qǐng)求委派給父類(lèi)加載器ExtClassLoader去完成;

第二:當(dāng)ExtClassLoader加載一個(gè)class時(shí),會(huì)把類(lèi)加載請(qǐng)求委派給BootStrapClassLoader去完成;

第三:如果BootStrapClassLoader加載失敗(例如在%JAVA_HOME%/jre/lib里未查找到該class),會(huì)使用ExtClassLoader來(lái)嘗試加載;

第四:如果ExtClassLoader也加載失敗,則會(huì)使用AppClassLoader來(lái)加載,如果AppClassLoader也加載失敗,則會(huì)報(bào)出異常ClassNotFoundException。

ClassLoader的雙親委派實(shí)現(xiàn)

ClassLoader通過(guò)loadClass()方法實(shí)現(xiàn)了雙親委托機(jī)制,用于類(lèi)的動(dòng)態(tài)加載。

該方法的源碼如下:

 
 
 
 
  1. protected Class loadClass(String name, boolean resolve)
  2.         throws ClassNotFoundException{
  3.         synchronized (getClassLoadingLock(name)) {
  4.             // First, check if the class has already been loaded
  5.             Class c = findLoadedClass(name);
  6.             if (c == null) {
  7.                 long t0 = System.nanoTime();
  8.                 try {
  9.                     if (parent != null) {
  10.                         c = parent.loadClass(name, false);
  11.                     } else {
  12.                         c = findBootstrapClassOrNull(name);
  13.                     }
  14.                 } catch (ClassNotFoundException e) {
  15.                     // ClassNotFoundException thrown if class not found
  16.                     // from the non-null parent class loader
  17.                 }
  18.                 if (c == null) {
  19.                     // If still not found, then invoke findClass in order
  20.                     // to find the class.
  21.                     long t1 = System.nanoTime();
  22.                     c = findClass(name);
  23.                     // this is the defining class loader; record the stats
  24.                     sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
  25.                     sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
  26.                     sun.misc.PerfCounter.getFindClasses().increment();
  27.                 }
  28.             }
  29.             if (resolve) {
  30.                 resolveClass(c);
  31.             }
  32.             return c;
  33.         }
  34.     }

loadClass方法本身是一個(gè)遞歸向上調(diào)用的過(guò)程,上述代碼中從parent.loadClass的調(diào)用就可以看出。

在執(zhí)行其他操作之前,首先通過(guò)findLoadedClass方法從最底端的類(lèi)加載器開(kāi)始檢查是否已經(jīng)加載指定的類(lèi)。如果已經(jīng)加載,則根據(jù)resolve參數(shù)決定是否要執(zhí)行連接過(guò)程,并返回Class對(duì)象。

而Jar包沖突往往發(fā)生在這里,當(dāng)?shù)谝粋€(gè)同名的類(lèi)被加載之后,在這一步檢查時(shí)就會(huì)直接返回,不會(huì)再加載真正需要的類(lèi)。那么,程序用到該類(lèi)時(shí)就會(huì)拋出找不到類(lèi),或找不到類(lèi)方法的異常。

Jar包的加載順序

上面已經(jīng)看到一旦一個(gè)類(lèi)被加載之后,全局限定名相同的類(lèi)可能就無(wú)法被加載了。而Jar包被加載的順序直接決定了類(lèi)加載的順序。

決定Jar包加載順序通常有以下因素:

  • 第一,Jar包所處的加載路徑。也就是加載該Jar包的類(lèi)加載器在JVM類(lèi)加載器樹(shù)結(jié)構(gòu)中所處層級(jí)。上面講到的四類(lèi)類(lèi)加載器加載的Jar包的路徑是有不同的優(yōu)先級(jí)的。
  • 第二,文件系統(tǒng)的文件加載順序。因Tomcat、Resin等容器的ClassLoader獲取加載路徑下的文件列表時(shí)是不排序的,這就依賴(lài)于底層文件系統(tǒng)返回的順序,當(dāng)不同環(huán)境之間的文件系統(tǒng)不一致時(shí),就會(huì)出現(xiàn)有的環(huán)境沒(méi)問(wèn)題,有的環(huán)境出現(xiàn)沖突。

本人遇到的問(wèn)題屬于第二種因素中的一個(gè)分支情況,即同一目錄下不同Jar包的加載順序不同。因此,通過(guò)調(diào)整Jar包的加載順序就暫時(shí)解決了問(wèn)題。

Jar包沖突的通常表現(xiàn)

Jar包沖突往往是很詭異的事情,也很難排查,但也會(huì)有一些共性的表現(xiàn)。

拋出java.lang.ClassNotFoundException:典型異常,主要是依賴(lài)中沒(méi)有該類(lèi)。導(dǎo)致原因有兩方面:第一,的確沒(méi)有引入該類(lèi);第二,由于Jar包沖突,Maven仲裁機(jī)制選擇了錯(cuò)誤的版本,導(dǎo)致加載的Jar包中沒(méi)有該類(lèi)。

  • 拋出java.lang.NoSuchMethodError:找不到特定的方法。Jar包沖突,導(dǎo)致選擇了錯(cuò)誤的依賴(lài)版本,該依賴(lài)版本中的類(lèi)對(duì)不存在該方法,或該方法已經(jīng)被升級(jí)。
  • 拋出java.lang.NoClassDefFoundError,java.lang.LinkageError等,原因同上。
  • 沒(méi)有異常但預(yù)期結(jié)果不同:加載了錯(cuò)誤的版本,不同的版本底層實(shí)現(xiàn)不同,導(dǎo)致預(yù)期結(jié)果不一致。

Tomcat啟動(dòng)時(shí)Jar包和類(lèi)的加載順序

最后,梳理一下Tomcat啟動(dòng)時(shí),對(duì)Jar包和類(lèi)的加載順序,其中包含上面提到的不同種類(lèi)的類(lèi)加載器默認(rèn)加載的目錄:

  • $java_home/lib 目錄下的java核心api;
  • $java_home/lib/ext 目錄下的java擴(kuò)展jar包;
  • java -classpath/-Djava.class.path所指的目錄下的類(lèi)與jar包;
  • $CATALINA_HOME/common目錄下按照文件夾的順序從上往下依次加載;
  • $CATALINA_HOME/server目錄下按照文件夾的順序從上往下依次加載;
  • $CATALINA_BASE/shared目錄下按照文件夾的順序從上往下依次加載;
  • 項(xiàng)目路徑/WEB-INF/classes下的class文件;
  • 項(xiàng)目路徑/WEB-INF/lib下的jar文件;

上述目錄中,同一文件夾下的Jar包,按照順序從上到下一次加載。如果一個(gè)class文件已經(jīng)被加載到JVM中,后面相同的class文件就不會(huì)被加載了。

小結(jié)

Jar包沖突在我們的日常開(kāi)發(fā)中是非常常見(jiàn)的問(wèn)題,如果能夠很好理解沖突的原因及底層機(jī)制,可以極大的提高解決問(wèn)題的能力和團(tuán)隊(duì)影響力。因此,在不少面試中都會(huì)被提及此類(lèi)問(wèn)題。

這篇文章我們重點(diǎn)講了手動(dòng)添加依賴(lài)情況下導(dǎo)致Jar包沖突的原因及解決方案。在解決該問(wèn)題時(shí)往往還會(huì)設(shè)計(jì)到Maven對(duì)Jar包沖突管理的一些策略,比如依賴(lài)傳遞原則、最短路徑優(yōu)先原則、最先聲明原則等,我們下篇文章再來(lái)詳細(xì)聊聊。


網(wǎng)站名稱(chēng):從Jar包沖突搞到類(lèi)加載機(jī)制,就是這么霸氣
分享網(wǎng)址:http://www.5511xx.com/article/dghhged.html