【Maven】jar包沖突原因與最優(yōu)解決方案
前言
你是否經(jīng)常遇到這樣的報(bào)錯(cuò):
java.lang.NoSuchMethodError
java.lang.ClassNotFoundException
java.lang.NoClassDefFoundError
以上報(bào)錯(cuò)就有可能是jar包沖突造成的,Maven中jar包沖突是開(kāi)發(fā)過(guò)程中比較常見(jiàn)而又令人頭疼的問(wèn)題,我們需要知道 jar包沖突的原理,才能更好的去解決jar包沖突的問(wèn)題。本文將從jar包沖突的原理和解決jar包沖突兩個(gè)方面闡述Maven中jar包問(wèn)題。
jar包沖突原因
當(dāng)我們?cè)趍aven項(xiàng)目中引入第三方組件時(shí),三方組件中的依賴可能會(huì)與項(xiàng)目已有組件發(fā)生沖突。
比如三方組件中依賴httpclient的版本是4.5.x,而項(xiàng)目中已有的httpclient版本是3.1.x,那么此時(shí)就會(huì)產(chǎn)生一下兩種情況:
如果用三方組件的高版本httpclient覆蓋原有的低版本httpclient,有可能會(huì)導(dǎo)致原來(lái)項(xiàng)目啟動(dòng)運(yùn)行失敗。即使高版本兼容低版本,這樣高風(fēng)險(xiǎn)的操作也是很危險(xiǎn)的;
如果在三方maven依賴中對(duì)其對(duì)依賴的httpclient在引入時(shí)使用進(jìn)行排除,使三方組件使用項(xiàng)目中的低版本httpclient,此時(shí)可能會(huì)因?yàn)榘姹静灰恢聦?dǎo)致三方組件無(wú)法使用
在這樣的情況下我們應(yīng)當(dāng)如何保證不影響項(xiàng)目原有依賴版本的情況下正常使用三方組件呢?此時(shí)可以考慮使用maven-shade-plugin插件,jar包沖突解決方案最后介紹。
依賴傳遞
首先我們需要了解jar包依賴的傳遞性。
關(guān)于依賴作用范圍詳解見(jiàn):【Maven】屬性scope依賴作用范圍詳解。
當(dāng)我們需要A的依賴的時(shí)候,就會(huì)在pom.xml中引入A的jar包;而引入的A的jar包中可能又依賴B的jar包,這樣Maven在解析pom.xml的時(shí)候,會(huì)依次將A、B 的jar包全部都引入進(jìn)來(lái)。
舉個(gè)例子:
在Spring Boot應(yīng)用中導(dǎo)入Hystrix和原生Guava的jar包:
利用Maven Helper插件得到項(xiàng)目導(dǎo)入的jar包依賴樹(shù):
從圖中可以看出Hystrix包含對(duì)Guava jar包依賴的引用: Hystrix -> Guava,所以在引入Hystrix的依賴的時(shí)候,會(huì)將Guava的依賴也引入進(jìn)來(lái)。
沖突原因
假設(shè)有如下依賴關(guān)系:
A->B->C->D1(log 15.0):A中包含對(duì)B的依賴,B中包含對(duì)C的依賴,C中包含對(duì)D1的依賴,假設(shè)是D1是日志jar包,version為15.0
E->F->D2(log 16.0):E中包含對(duì)F的依賴,F(xiàn)包含對(duì)D2的依賴,假設(shè)是D2是同一個(gè)日志jar包,version為16.0
當(dāng)pom.xml文件中引入A、E兩個(gè)依賴后,根據(jù)Maven傳遞依賴的原則,D1、D2都會(huì)被引入,而D1、D2是同一個(gè)依賴D的不同版本。
當(dāng)我們?cè)谡{(diào)用D2中的method1()方法,而D1中是15.0版本(method1可能是D升級(jí)后增加的方法),可能沒(méi)有這個(gè)方法,這樣JVM在加載A中D1依賴的時(shí)候,找不到method1方法,就會(huì)報(bào)NoSuchMethodError的錯(cuò)誤,此時(shí)就產(chǎn)生了jar包沖突。
注:
如果在調(diào)用method2()方法的時(shí)候,D1、D2都含有這個(gè)方法(且升級(jí)的版本D2沒(méi)有改動(dòng)這個(gè)方法,這樣即使D有多個(gè)版本,也不會(huì)產(chǎn)生版本沖突的問(wèn)題。)
舉個(gè)例子:
利用Maven Helper插件分析得出:Guava這個(gè)依賴包產(chǎn)生沖突。
我們之前導(dǎo)入了Guava的原生jar包,版本號(hào)是20.0;而現(xiàn)在提示Guava產(chǎn)生沖突,且沖突發(fā)生位置是Hystrix所在的jar包,所以可以猜測(cè)Hystrix中包含了對(duì)Guava不同版本的jar包的引用。
為了驗(yàn)證我們的猜想,使用Maven Helper插件打印出Hystrix依賴的jar tree:
可以看到:Hystrix jar中所依賴的Guava jar包是15.0版本的,而我們之前在pom.xml中引入的原生Guava jar包是20.0版本的,這樣Guava就有15.0 與20.0這兩個(gè)版本,因此發(fā)生了jar包沖突。
jar包沖突解決方案
Maven 解析 pom.xml 文件時(shí),同一個(gè) jar 包只會(huì)保留一個(gè),那么面對(duì)多個(gè)版本的jar包,需要怎么解決呢?
Maven默認(rèn)處理策略
最短路徑優(yōu)先
Maven 面對(duì) D1 和 D2 時(shí),會(huì)默認(rèn)選擇最短路徑的那個(gè) jar 包,
即 D2。E->F->D2 比 A->B->C->D1 路徑短1。
最先聲明優(yōu)先
如果路徑一樣的話,如: A->B->C1, E->F->C2 ,兩個(gè)依賴路徑長(zhǎng)度都是 2,那么就選擇最先聲明。
排除依賴
移除依賴:用于排除某項(xiàng)依賴的依賴jar包
1.我們可以借助Maven Helper插件中的Dependency Analyzer分析沖突的jar包,然后在對(duì)應(yīng)標(biāo)紅版本的jar包上面點(diǎn)擊execlude,就可以將該jar包排除出去。
再刷新以后沖突就會(huì)消失。
2.手動(dòng)排除
手動(dòng)在pom.xml中使用<exclusion>
標(biāo)簽去排除沖突的jar包(上面利用插件Maven Helper中的execlude方法其實(shí)等同于方法1):
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>1.4.4.RELEASE</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
mvn分析包沖突命令:
mvn dependency:tree
版本鎖定
版本鎖定原則:一般用在繼承項(xiàng)目的父項(xiàng)目中
正常項(xiàng)目都是多模塊的項(xiàng)目,如moduleA和moduleB共同依賴X這個(gè)依賴的話,那么可以將X抽取出來(lái),同時(shí)設(shè)置其版本號(hào),這樣X(jué)依賴在升級(jí)的時(shí)候,不需要分別對(duì)moduleA和moduleB模塊中的依賴X進(jìn)行升級(jí),避免太多地方(moduleC、moduleD…)引用X依賴的時(shí)候忘記升級(jí)造成jar包沖突,這也是實(shí)際項(xiàng)目開(kāi)發(fā)中比較常見(jiàn)的方法。
首先定義一個(gè)父pom.xml,將公共依賴放在該pom.xml中進(jìn)行聲明:
<properties>
<spring.version>spring4.2.4</spring.version>
<properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.versio}</version>
</dependency>
</dependencies>
</dependencyManagement>
這樣如moduleA和moduleB在引用Spring-beans jar包的時(shí)候,直接使用父pom.xml中定義的公共依賴就可以:
moduleA在其pom.xml使用spring-bean的jar包(不用再定義版本):
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
</dependencies>
moduleB在其pom.xml使用spring-bean的jar包如上類似:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
</dependencies>
maven-shade-plugin插件
作用是將依賴的包在package階段一起打入jar包中,以及對(duì)依賴的jar包進(jìn)行重命名從而達(dá)到隔離的作用。這里為了解決上面的問(wèn)題我們主要使用第二個(gè)功能特性,使得相同依賴不同版本達(dá)到共存的目的。
1.環(huán)境準(zhǔn)備
這里用fastjson來(lái)模擬使用maven-shade-plugin解決項(xiàng)目中不同版本共存問(wèn)題。原項(xiàng)目此時(shí)使用的是1.1.15版本的fastjson
<!-- 原項(xiàng)目 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.1.15</version>
</dependency>
假引入一個(gè)三方依賴,該依賴使用1.2.75版本的fastjson
```xml
<!-- 將引入依賴 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
2.解決方案
搭建一個(gè)新的模塊rename-dependencies,專門用于存放1.2.75依賴。在pom文件中添加1.2.75的依賴,然后添加maven-shade-plugin插件。rename-dependencies的pom如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<groupId>com.sk</groupId>
<artifactId>rename-dependencies</artifactId>
<version>1.0-SNAPSHOT</version>
<modelVersion>4.0.0</modelVersion>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<relocations>
<relocation>
<pattern>com.alibaba</pattern>
<shadedPattern>shade.com.alibaba</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
從配置文件中可以看到,由于maven-shade-plugin插件在解決這個(gè)問(wèn)題上其實(shí)是通過(guò)對(duì)依賴進(jìn)行重命名而達(dá)到隔離的目的,所以配置主要是集中在relocations中。這里將以com.alibaba開(kāi)頭的包全部重命名為以shade.com.alibaba開(kāi)頭。
3.引入依賴
將rename-dependencies進(jìn)行打包,打包好之后在原項(xiàng)目中引入rename-dependencies的依賴。此時(shí)在引入rename-dependencies之后,可以在項(xiàng)目下看到該依賴中的fastjson包名發(fā)生了變化
此時(shí)在代碼中調(diào)用fastjson相關(guān)方法,會(huì)提示選擇所需要包,如下圖,此時(shí)問(wèn)題解決,兩個(gè)版本的fastjson可同時(shí)使用已經(jīng)兼容。
一些需要注意的坑
描述: 引入依賴找不到重命名的shade包
原因:重命名的模塊和需要引入依賴的模塊在一個(gè)項(xiàng)目中,idea優(yōu)先找本項(xiàng)目,所以沒(méi)有走倉(cāng)庫(kù)
解決方案:
將模塊從項(xiàng)目maven中移除,右鍵項(xiàng)目-maven-unlink maven projects
新建一個(gè)項(xiàng)目專門來(lái)做依賴
總結(jié)
本文從jar包沖突的原理和解決jar包沖突兩個(gè)方面闡述Maven引入jar包依賴的問(wèn)題;
其中在解決方案選擇方面:
如果Maven不能根據(jù)默認(rèn)處理策略解決掉,就需要從移除依賴或者升級(jí)現(xiàn)有依賴處理;
但是升級(jí)現(xiàn)有依賴風(fēng)險(xiǎn)比較大,有時(shí)會(huì)對(duì)原項(xiàng)目不兼容的代碼進(jìn)行大量修改,就比如有次項(xiàng)目引入封裝的工作流組件時(shí),其中組件內(nèi)使用的mybatis-plus為3.5.1版本,原項(xiàng)目使用的3.3.1,先是對(duì)組件排除掉mybatis-plus,但是后面發(fā)現(xiàn)mybatis-plus中有一個(gè)反射工具類無(wú)法使用,只有高版本才能使用,于是就對(duì)原項(xiàng)目做依賴升級(jí),但是需要對(duì)原項(xiàng)目不兼容的代碼進(jìn)行大量修改,比如mybatisconfig類和大量接口返回類型修改;
最優(yōu)選擇:maven-shade-plugin 保證不影響項(xiàng)目原有依賴版本的情況下正常使用三方組件。
參考:
https://blog.csdn.net/qq_38550836/article/details/111567355文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-421876.html
https://blog.csdn.net/noaman_wgs/article/details/81137893文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-421876.html
到了這里,關(guān)于【Maven】jar包沖突原因與最優(yōu)解決方案的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!