<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Jar包沖突問題及解決方案!

          共 11329字,需瀏覽 23分鐘

           ·

          2021-05-18 21:15

          作者:sherlockyb
          鏈接:https://www.jianshu.com/p/100439269148

          Jar包沖突是老生常談的問題,幾乎每一個Java程序猿都不可避免地遇到過,并且也都能想到通常的原因一般是同一個Jar包由于maven傳遞依賴等原因被引進了多個不同的版本而導(dǎo)致,可采用依賴排除、依賴管理等常規(guī)方式來嘗試解決該問題,但這些方式真正能徹底解決該沖突問題嗎?答案是否定的。筆者之所以將文章題目起為“重新看待”,是因為之前對于Jar包沖突問題的理解僅僅停留在前面所說的那些,直到在工作中遇到的一系列Jar包沖突問題后,才發(fā)現(xiàn)并不是那么簡單,對該問題有了重新的認識,接下來本文將圍繞Jar包沖突的問題本質(zhì)和相關(guān)的解決方案這兩個點進行闡述。

          Jar包沖突問題

          一、沖突的本質(zhì)

          Jar包沖突的本質(zhì)是什么?Google了半天也沒找到一個讓人滿意的完整定義。其實,我們可以從Jar包沖突產(chǎn)生的結(jié)果來總結(jié),在這里給出如下定義(此處如有不妥,歡迎拍磚-):

          Java應(yīng)用程序因某種因素,加載不到正確的類而導(dǎo)致其行為跟預(yù)期不一致。

          具體來說可分為兩種情況:1)應(yīng)用程序依賴的同一個Jar包出現(xiàn)了多個不同版本,并選擇了錯誤的版本而導(dǎo)致JVM加載不到需要的類或加載了錯誤版本的類,為了敘述的方便,筆者稱之為第一類Jar包沖突問題;2)同樣的類(類的全限定名完全一樣)出現(xiàn)在多個不同的依賴Jar包中,即該類有多個版本,并由于Jar包加載的先后順序?qū)е翵VM加載了錯誤版本的類,稱之為第二類Jar包問題。這兩種情況所導(dǎo)致的結(jié)果其實是一樣的,都會使應(yīng)用程序加載不到正確的類,那其行為自然會跟預(yù)期不一致了,以下對這兩種類型進行詳細分析。

          1.1 同一個Jar包出現(xiàn)了多個不同版本

          隨著Jar包迭代升級,我們所依賴的開源的或公司內(nèi)部的Jar包工具都會存在若干不同的版本,而版本升級自然就避免不了類的方法簽名變更,甚至于類名的更替,而我們當(dāng)前的應(yīng)用程序往往依賴特定版本的某個類 M ,由于maven的傳遞依賴而導(dǎo)致同一個Jar包出現(xiàn)了多個版本,當(dāng)maven的仲裁機制選擇了錯誤的版本時,而恰好類 M在該版本中被去掉了,或者方法簽名改了,導(dǎo)致應(yīng)用程序因找不到所需的類 M或找不到類 M中的特定方法,就會出現(xiàn)第一類Jar沖突問題??煽偨Y(jié)出該類沖突問題發(fā)生的以下三個必要條件:

          • 由于maven的傳遞依賴導(dǎo)致依賴樹中出現(xiàn)了同一個Jar包的多個版本

          • 該Jar包的多個版本之間存在接口差異,如類名更替,方法簽名更替等,且應(yīng)用程序依賴了其中有變更的類或方法

          • maven的仲裁機制選擇了錯誤的版本

          1.2 同一個類出現(xiàn)在多個不同Jar包中

          同樣的類出現(xiàn)在了應(yīng)用程序所依賴的兩個及以上的不同Jar包中,這會導(dǎo)致什么問題呢?我們知道,同一個類加載器對于同一個類只會加載一次(多個不同類加載器就另說了,這也是解決Jar包沖突的一個思路,后面會談到),那么當(dāng)一個類出現(xiàn)在了多個Jar包中,假設(shè)有 A 、 BC 等,由于Jar包依賴的路徑長短、聲明的先后順序或文件系統(tǒng)的文件加載順序等原因,類加載器首先從Jar包 A 中加載了該類后,就不會加載其余Jar包中的這個類了,那么問題來了:如果應(yīng)用程序此時需要的是Jar包 B 中的類版本,并且該類在Jar包 AB 中有差異(方法不同、成員不同等等),而JVM卻加載了Jar包 A 的中的類版本,與期望不一致,自然就會出現(xiàn)各種詭異的問題。

          從上面的描述中,可以發(fā)現(xiàn)出現(xiàn)不同Jar包的沖突問題有以下三個必要條件:

          • 同一個類 M 出現(xiàn)在了多個依賴的Jar包中,為了敘述方便,假設(shè)還是兩個:AB

          • Jar包 AB 中的該類 M 有差異,無論是方法簽名不同也好,成員變量不同也好,只要可以造成實際加載的類的行為和期望不一致都行。如果說Jar包 AB 中的該類完全一樣,那么類加載器無論先加載哪個Jar包,得到的都是同樣版本的類 M ,不會有任何影響,也就不會出現(xiàn)Jar包沖突帶來的詭異問題。

          • 加載的類 M 不是所期望的版本,即加載了錯誤的Jar包

          二、沖突的產(chǎn)生原因

          2.1 maven仲裁機制

          當(dāng)前maven大行其道,說到第一類Jar包沖突問題的產(chǎn)生原因,就不得不提maven的依賴機制了。傳遞性依賴是Maven2.0引入的新特性,讓我們只需關(guān)注直接依賴的Jar包,對于間接依賴的Jar包,Maven會通過解析從遠程倉庫獲取的依賴包的pom文件來隱式地將其引入,這為我們開發(fā)帶來了極大的便利,但與此同時,也帶來了常見的問題——版本沖突,即同一個Jar包出現(xiàn)了多個不同的版本,針對該問題Maven也有一套仲裁機制來決定最終選用哪個版本,但Maven的選擇往往不一定是我們所期望的,這也是產(chǎn)生Jar包沖突最常見的原因之一。先來看下Maven的仲裁機制:

          • 優(yōu)先按照依賴管理元素中指定的版本聲明進行仲裁,此時下面的兩個原則都無效了

          • 若無版本聲明,則按照“短路徑優(yōu)先”的原則(Maven2.0)進行仲裁,即選擇依賴樹中路徑最短的版本

          • 若路徑長度一致,則按照“第一聲明優(yōu)先”的原則進行仲裁,即選擇POM中最先聲明的版本

          從maven的仲裁機制中可以發(fā)現(xiàn),除了第一條仲裁規(guī)則(這也是解決Jar包沖突的常用手段之一)外,后面的兩條原則,對于同一個Jar包不同版本的選擇,maven的選擇有點“一廂情愿”了,也許這是maven研發(fā)團隊在總結(jié)了大量的項目依賴管理經(jīng)驗后得出的兩條結(jié)論,又或者是發(fā)現(xiàn)根本找不到一種統(tǒng)一的方式來滿足所有場景之后的無奈之舉,可能這對于多數(shù)場景是適用的,但是它不一定適合我——當(dāng)前的應(yīng)用,因為每個應(yīng)用都有其特殊性,該依賴哪個版本,maven沒辦法幫你完全搞定,如果你沒有規(guī)規(guī)矩矩地使用來進行依賴管理,就注定了逃脫不了第一類Jar包沖突問題。

          2.1 Jar包的加載順序

          對于第二類Jar包沖突問題,即多個不同的Jar包有類沖突,這相對于第一類問題就顯得更為棘手。為什么這么說呢?在這種情況下,兩個不同的Jar包,假設(shè)為 AB,它們的名稱互不相同,甚至可能完全不沾邊,如果不是出現(xiàn)沖突問題,你可能都不會發(fā)現(xiàn)它們有共有的類!對于A、B這兩個Jar包,maven就顯得無能為力了,因為maven只會為你針對同一個Jar包的不同版本進行仲裁,而這倆是屬于不同的Jar包,超出了maven的依賴管理范疇。此時,當(dāng)A、B都出現(xiàn)在應(yīng)用程序的類路徑下時,就會存在潛在的沖突風(fēng)險,即A、B的加載先后順序就決定著JVM最終選擇的類版本,如果選錯了,就會出現(xiàn)詭異的第二類沖突問題。

          那么Jar包的加載順序都由哪些因素決定的呢?具體如下:

          • Jar包所處的加載路徑,或者換個說法就是加載該Jar包的類加載器在JVM類加載器樹結(jié)構(gòu)中所處層級。由于JVM類加載的雙親委派機制,層級越高的類加載器越先加載其加載路徑下的類,顧名思義,引導(dǎo)類加載器(bootstrap ClassLoader,也叫啟動類加載器)是最先加載其路徑下Jar包的,其次是擴展類加載器(extension ClassLoader),再次是系統(tǒng)類加載器(system ClassLoader,也就是應(yīng)用加載器appClassLoader),Jar包所處加載路徑的不同,就決定了它的加載順序的不同。比如我們在eclipse中配置web應(yīng)用的resin環(huán)境時,對于依賴的Jar包是添加到Bootstrap Entries中還是User Entries中呢,則需要仔細斟酌下咯。

          • 文件系統(tǒng)的文件加載順序。這個因素很容易被忽略,而往往又是因環(huán)境不一致而導(dǎo)致各種詭異沖突問題的罪魁禍?zhǔn)?。因tomcat、resin等容器的ClassLoader獲取加載路徑下的文件列表時是不排序的,這就依賴于底層文件系統(tǒng)返回的順序,那么當(dāng)不同環(huán)境之間的文件系統(tǒng)不一致時,就會出現(xiàn)有的環(huán)境沒問題,有的環(huán)境出現(xiàn)沖突。例如,對于Linux操作系統(tǒng),返回順序則是由iNode的順序來決定的,如果說測試環(huán)境的Linux系統(tǒng)與線上環(huán)境不一致時,就極有可能出現(xiàn)典型案例:測試環(huán)境怎么測都沒問題,但一上線就出現(xiàn)沖突問題,規(guī)避這種問題的最佳辦法就是盡量保證測試環(huán)境與線上一致。

          三、沖突的表象

          Jar包沖突可能會導(dǎo)致哪些問題?通常發(fā)生在編譯或運行時,主要分為兩類問題:一類是比較直觀的也是最為常見的錯誤是拋出各種運行時異常,還有一類就是比較隱晦的問題,它不會報錯,其表現(xiàn)形式是應(yīng)用程序的行為跟預(yù)期不一致,分條羅列如下:

          • java.lang.ClassNotFoundException,即java類找不到。這類典型異常通常是由于,沒有在依賴管理中聲明版本,maven的仲裁的時候選取了錯誤的版本,而這個版本缺少我們需要的某個class而導(dǎo)致該錯誤。例如httpclient-4.4.jar升級到httpclient-4.36.jar時,類org.apache.http.conn.ssl.NoopHostnameVerifier被去掉了,如果此時我們本來需要的是4.4版本,且用到了NoopHostnameVerifier這個類,而maven仲裁時選擇了4.6,則會導(dǎo)致ClassNotFoundException異常。

          • java.lang.NoSuchMethodError,即找不到特定方法,第一類沖突和第二類沖突都可能導(dǎo)致該問題——加載的類不正確。若是第一類沖突,則是由于錯誤版本的Jar包與所需要版本的Jar包中的類接口不一致導(dǎo)致,例如antlr-2.7.2.jar升級到antlr-2.7.6.Jar時,接口antlr.collections.AST.getLine()發(fā)生變動,當(dāng)maven仲裁選擇了錯誤版本而加載了錯誤版本的類AST,則會導(dǎo)致該異常;若是第二類沖突,則是由于不同Jar包含有的同名類接口不一致導(dǎo)致,典型的案例:Apache的commons-lang包,2.x升級到3.x時,包名直接從commons-lang改為commons-lang3,部分接口也有所改動,由于包名不同和傳遞性依賴,經(jīng)常會出現(xiàn)兩種Jar包同時在classpath下,org.apache.commons.lang.StringUtils.isBlank就是其中有差異的接口之一,由于Jar包的加載順序,導(dǎo)致加載了錯誤版本的StringUtils類,就可能出現(xiàn)NoSuchMethodError異常。

          • java.lang.NoClassDefFoundError,java.lang.LinkageError等,原因和上述雷同,就不作具體案例分析了。

          • 沒有報錯異常,但應(yīng)用的行為跟預(yù)期不一致。這類問題同樣也是由于運行時加載了錯誤版本的類導(dǎo)致,但跟前面不同的是,沖突的類接口都是一致的,但具體實現(xiàn)邏輯有差異,當(dāng)我們加載的類版本不是我們需要的實現(xiàn)邏輯,就會出現(xiàn)行為跟預(yù)期不一致問題。這類問題通常發(fā)生在我們自己內(nèi)部實現(xiàn)的多個Jar包中,由于包路徑和類名命名不規(guī)范等問題,導(dǎo)致兩個不同的Jar包出現(xiàn)了接口一致但實現(xiàn)邏輯又各不相同的同名類,從而引發(fā)此問題。

          解決方案

          一、問題排查和解決

          1. 如果有異常堆棧信息,根據(jù)錯誤信息即可定位導(dǎo)致沖突的類名,然后在eclipse中CTRL+SHIFT+T或者在idea中CTRL+N就可發(fā)現(xiàn)該類存在于多個依賴Jar包中

          2. 若步驟1無法定位沖突的類來自哪個Jar包,可在應(yīng)用程序啟動時加上JVM參數(shù)-verbose:class或者-XX:+TraceClassLoading,日志里會打印出每個類的加載信息,如來自哪個Jar包

          3. 定位了沖突類的Jar包之后,通過mvn dependency:tree -Dverbose -Dincludes=:查看是哪些地方引入的Jar包的這個版本

          4. 確定Jar包來源之后,如果是第一類Jar包沖突,則可用排除不需要的Jar包版本或者在依賴管理中申明版本;若是第二類Jar包沖突,如果可排除,則用排掉不需要的那個Jar包,若不能排,則需考慮Jar包的升級或換個別的Jar包。當(dāng)然,除了這些方法,還可以從類加載器的角度來解決該問題,可參考博文——如果jar包沖突不可避免,如何實現(xiàn)jar包隔離,其思路值得借鑒。

          二、有效避免

          從上一節(jié)的解決方案可以發(fā)現(xiàn),當(dāng)出現(xiàn)第二類Jar包沖突,且沖突的Jar包又無法排除時,問題變得相當(dāng)棘手,這時候要處理該沖突問題就需要較大成本了,所以,最好的方式是在沖突發(fā)生之前能有效地規(guī)避之!就好比數(shù)據(jù)庫死鎖問題,死鎖避免和死鎖預(yù)防就顯得相當(dāng)重要,若是等到真正發(fā)生死鎖了,常規(guī)的做法也只能是回滾并重啟部分事務(wù),這就捉襟見肘了。那么怎樣才能有效地規(guī)避Jar包沖突呢?

          2.1 良好的習(xí)慣:依賴管理

          對于第一類Jar包沖突問題,通常的做法是用排除不需要的版本,但這種做法帶來的問題是每次引入帶有傳遞性依賴的Jar包時,都需要一一進行排除,非常麻煩。maven為此提供了集中管理依賴信息的機制,即依賴管理元素,對依賴Jar包進行統(tǒng)一版本管理,一勞永逸。通常的做法是,在parent模塊的pom文件中盡可能地聲明所有相關(guān)依賴Jar包的版本,并在子pom中簡單引用該構(gòu)件即可。

          來看個示例,當(dāng)開發(fā)時確定使用的httpclient版本為4.5.1時,可在父pom中配置如下:

          ...
              <properties>
                <httpclient.version>4.5.1</httpclient.version>
              </properties>
              <dependencyManagement>
                <dependencies>
                  <dependency>
                    <groupId>org.apache.httpcomponents</groupId>
                    <artifactId>httpclient</artifactId>
                    <version>${httpclient.version}</version>
                  </dependency>
                </dependencies>
              </dependencyManagement>
          ...

          然后各個需要依賴該Jar包的子pom中配置如下依賴:

          ...
              <dependencies>
                <dependency>
                  <groupId>org.apache.httpcomponents</groupId>
                  <artifactId>httpclient</artifactId>
                </dependency>
              </dependencies>
          ...
          2.2 沖突檢測插件

          對于第二類Jar包沖突問題,前面也提到過,其核心在于同名類出現(xiàn)在了多個不同的Jar包中,如果人工來排查該問題,則需要逐個點開每個Jar包,然后相互對比看有沒同名的類,那得多么浪費精力???!好在這種費時費力的體力活能交給程序去干。maven-enforcer-plugin,這個強大的maven插件,配合extra-enforcer-rules工具,能自動掃描Jar包將沖突檢測并打印出來,汗顏的是,筆者工作之前居然都沒聽過有這樣一個插件的存在,也許是沒遇到像工作中這樣的沖突問題,算是漲姿勢了。其原理其實也比較簡單,通過掃描Jar包中的class,記錄每個class對應(yīng)的Jar包列表,如果有多個即是沖突了,故不必深究,我們只需要關(guān)注如何用它即可。

          最終需要打包運行的應(yīng)用模塊pom中,引入maven-enforcer-plugin的依賴,在build階段即可發(fā)現(xiàn)問題,并解決它。比如對于具有parent pom的多模塊項目,需要將插件依賴聲明在應(yīng)用模塊的pom中。這里有童鞋可能會疑問,為什么不把插件依賴聲明在parent pom中呢?那樣依賴它的應(yīng)用子模塊豈不是都能復(fù)用了?這里之所以強調(diào)“打包運行的應(yīng)用模塊pom”,是因為沖突檢測針對的是最終集成的應(yīng)用,關(guān)注的是應(yīng)用運行時是否會出現(xiàn)沖突問題,而每個不同的應(yīng)用模塊,各自依賴的Jar包集合是不同的,由此而產(chǎn)生的列表也是有差異的,因此只能針對應(yīng)用模塊pom分別引入該插件。

          先看示例用法如下:

          ...
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-enforcer-plugin</artifactId>
            <version>1.4.1</version>
            <executions>
              <execution>
                <id>enforce</id>
                <configuration>
                  <rules>
                    <dependencyConvergence/>
                  </rules>
                </configuration>
                <goals>
                  <goal>enforce</goal>
                </goals>
              </execution>
              <execution>
                <id>enforce-ban-duplicate-classes</id>
                <goals>
                  <goal>enforce</goal>
                </goals>
                <configuration>
                  <rules>
                    <banDuplicateClasses>
                      <ignoreClasses>
                        <ignoreClass>javax.*</ignoreClass>
                        <ignoreClass>org.junit.*</ignoreClass>
                        <ignoreClass>net.sf.cglib.*</ignoreClass>
                        <ignoreClass>org.apache.commons.logging.*</ignoreClass>
                        <ignoreClass>org.springframework.remoting.rmi.RmiInvocationHandler</ignoreClass>
                      </ignoreClasses>
                      <findAllDuplicates>true</findAllDuplicates>
                    </banDuplicateClasses>
                  </rules>
                  <fail>true</fail>
                </configuration>
              </execution>
            </executions>
            <dependencies>
              <dependency>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>extra-enforcer-rules</artifactId>
                <version>1.0-beta-6</version>
              </dependency>
            </dependencies>
          </plugin>

          maven-enforcer-plugin是通過很多預(yù)定義的標(biāo)準(zhǔn)規(guī)則(standard rules)和用戶自定義規(guī)則,來約束maven的環(huán)境因素,如maven版本、JDK版本等等,它有很多好用的特性,具體可參見官網(wǎng)。而Extra Enforcer Rules則是MojoHaus項目下的針對maven-enforcer-plugin而開發(fā)的提供額外規(guī)則的插件,這其中就包含前面所提的重復(fù)類檢測功能,具體用法可參見官網(wǎng),這里就不詳細敘述了。

          典型案例

          第一類Jar包沖突

          這類Jar包沖突是最常見的也是相對比較好解決的,已經(jīng)在三、沖突的表象這節(jié)中列舉了部分案例,這里就不重復(fù)列舉了。

          第二類Jar包沖突

          Spring2.5.6與Spring3.x

          Spring2.5.6與Spring3.x,從單模塊拆分為多模塊,Jar包名稱(artifactId)也從spring變?yōu)閟pring-submoduleName,如
          spring-context、spring-aop等等,并且也有少部分接口改動(Jar包升級的過程中,這也是在所難免的)。由于是不同的Jar包,經(jīng)maven的傳遞依賴機制,就會經(jīng)常性的存在這倆版本的Spring都在classpath中,從而引發(fā)潛在的沖突問題。

          推薦閱讀:

          世界的真實格局分析,地球人類社會底層運行原理

          企業(yè)IT技術(shù)架構(gòu)規(guī)劃方案

          論數(shù)字化轉(zhuǎn)型——轉(zhuǎn)什么,如何轉(zhuǎn)?

          企業(yè)10大管理流程圖,數(shù)字化轉(zhuǎn)型從業(yè)者必備!

          【中臺實踐】華為大數(shù)據(jù)中臺架構(gòu)分享.pdf

          數(shù)字化轉(zhuǎn)型的本質(zhì)(10個關(guān)鍵詞)

          小米用戶畫像實戰(zhàn),48頁PPT下載

          華為大數(shù)據(jù)解決方案(PPT)

          瀏覽 78
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  男女拍拍视频 j.zyme.xin | 蜜桃AV鲁一鲁 | 无码av免费在线观看 | 网站黄色小视频 | 国产黄色小视频 |