舉例
A依賴于B及C,而B又依賴于X、Y,而C依賴于X、M,則A除引B及C的依賴包下,還會(huì)引入X,Y,M的依賴包(一般情況下了,Maven可通過<scope>等若干種方式控制傳遞依賴)。
這里有一個(gè)需要特別注意的,即B和C同時(shí)依賴于X,假設(shè)B依賴于X的1.0版本,而C依賴于X的2.0版本,A究竟依賴于X的1.0還是2.0版本呢?
這就看Classloader的加載順序了,假設(shè)Classloader先加載X_1.0,而它就不會(huì)再加載X_2.0了,如果A恰恰希望使用X_2.0呢,血案就這樣不期而遇了。
?
比如 A 依賴 版本為2.0 的 C ,B 依賴 版本為3.0的 C。在你的pom中,你同時(shí)依賴了 A 和 B ,這時(shí)就會(huì)產(chǎn)生沖突。這時(shí)候你就要判斷,哪個(gè)版本能同時(shí)讓A和B工作(如果可以的話),然后排除掉另一個(gè)就行了。我通常都是排除掉較低的版本。?
<dependencies> <dependency> <groupId>A</groupId> <artifactId>A</artifactId> <version>xxx</version> <exclusions> <exclusion> <groupId>C</groupId> <artifactId>C</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>B</groupId> <artifactId>B</artifactId> </dependency> </dependencies>
?
理包依賴是 Maven 核心功能之一,下面通過如何引入 jar 包;如何解析 jar 包依賴;包沖突是如何產(chǎn)生;如何解決包沖突;依賴管理解決什么問題;什么是依賴范圍;使用包依賴的最佳實(shí)踐等 6 個(gè)問題來介紹。
如何引入 jar 包
在代碼開發(fā)時(shí),如果需要使用第三方 jar 包提供的類庫,那么需要在 pom.xml 加入該 jar 包依賴。 例如:使用 zookeeper client
<dependencies> <!-- https://mvnrepository.com/artifact/org.apache.hadoop/zookeeper --> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>zookeeper</artifactId> <version>3.3.1</version> </dependency> </dependencies>
?
Maven 如何解析 jar 包依賴——傳遞依賴
如上所述,在 pom.xml 中引入 zookeeper jar 包依賴,當(dāng) Maven 解析該依賴時(shí),需要引入的 jar 包不僅僅只有 zookeeper,還會(huì)有 zookeeper 內(nèi)部依賴的 jar 包,還會(huì)有 zookeeper 內(nèi)部依賴的 jar 包依賴的 jar 包......,依賴關(guān)系不斷傳遞,直至沒有依賴。
例如:上述 pom.xml 引入 zookeeper 依賴,實(shí)際引入的 jar 包有
包沖突如何產(chǎn)生?
舉個(gè)?:假設(shè) A->B->C->D1, E->F->D2,D1,D2 分別為 D 的不同版本。
如果 pom.xml 文件中引入了 A 和 E 之后,按照 Maven 傳遞依賴原則,工程內(nèi)需要引入的實(shí)際 Jar 包將會(huì)有:A B C D1 和 E F D2,因此 D1,D2 將會(huì)產(chǎn)生包沖突。
如何解決包沖突
Maven 解析 pom.xml 文件時(shí),同一個(gè) jar 包只會(huì)保留一個(gè),這樣有效的避免因引入兩個(gè) jar 包導(dǎo)致的工程運(yùn)行不穩(wěn)定性。
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)先
如果路徑一樣的話,舉個(gè)?: A->B->C1, E->F->C2 ,兩個(gè)依賴路徑長度都是 2,那么就選擇最先聲明。
移除依賴
如果我們不想通過 A->B->->D1 引入 D1 的話,那么我們?cè)诼暶饕?A 的時(shí)候?qū)?D1 排除掉,這樣也避免了包沖突。
舉個(gè)?:將 zookeeper 的 jline 依賴排除 用exclusions標(biāo)簽
<dependency> <groupId>org.apache.hadoop</groupId> <artifactId>zookeeper</artifactId> <version>3.3.1</version> <exclusions> <exclusion> <groupId>jline</groupId> <artifactId>jline</artifactId> </exclusion> </exclusions> </dependency>
?
檢測包沖突工具
mvn dependency:help
mvn dependency:analyze
mvn dependency:tree
mvn dependency:tree -Dverbose
詳細(xì)參考:mvn dependency
mvn dependency:tree
依賴管理解決什么問題
當(dāng)同一個(gè)工程內(nèi)有多個(gè)模塊時(shí),并且要求多個(gè)模塊使用某個(gè) jar 包的相同版本,為了方便統(tǒng)一版本號(hào),升級(jí)版本號(hào),需要提取出一個(gè)父親模塊來管理子模塊共同依賴的 jar 包版本。
舉個(gè)?:有兩個(gè)模塊 projectA, projectB,它們的依賴分別如下所示:
projectA:
<project>
...
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-a</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>group-c</groupId>
<artifactId>excluded-artifact</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>bar</type>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
?
projectB:
<project>
...
<dependencies>
<dependency>
<groupId>group-c</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>bar</type>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
?
projectA 和 projectB 共同依賴了 group-a/artifact-b/1.0,提取公共依賴,生成 parent, parent 依賴如下:
<project>
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>bar</type>
<scope>runtime</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
?
則 projectA 和 projectB 均不需要指定 group-a/artifact-b 的 version 信息,未來升級(jí) version 信息時(shí),只需要在 parent 內(nèi)部指定。
projectA:
<project>
...
<parent>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
</parent>
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-a</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>group-c</groupId>
<artifactId>excluded-artifact</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
</dependency>
</dependencies>
</project>
?
projectB:
<project>
...
<dependencies>
<dependency>
<groupId>group-c</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
</dependency>
</dependencies>
</project>
?
依賴范圍
如果不顯示執(zhí)行 <scope> 屬性時(shí),默認(rèn) <scope>compile</scope>。
scope 有哪些屬性:compile, provided, runtime, test, system 等。
詳細(xì)參考:依賴范圍
最佳實(shí)踐
(1)項(xiàng)目中源代碼使用的 jar 包一定在 pom.xml 中顯示引用。
(2)經(jīng)常 check 一下包沖突,檢查是否需要處理。
(3)當(dāng)使用多個(gè)模塊時(shí),parent 一定要使用包管理模塊來規(guī)范 Jar 包版本,而不是包依賴模塊直接引入依賴。 dependencyManagement vs dependencies
?
?
?
?第二個(gè)問題:
第一板斧:找到傳遞依賴的鬼出在哪里?
dependency:tree是把照妖照,pom.xml用它照照,所有傳遞性依賴都將無處遁形,并且會(huì)以層級(jí)樹方式展現(xiàn),非常直觀。
以下就是執(zhí)行dependency:tree后的一個(gè)輸出:
引用
[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ euler-foundation --- [INFO] com.hsit:euler-foundation:jar:0.9.0.1-SNAPSHOT [INFO] +- com.rop:rop:jar:1.0.1:compile [INFO] | +- org.slf4j:slf4j-api:jar:1.7.5:compile [INFO] | +- org.slf4j:slf4j-log4j12:jar:1.7.5:compile [INFO] | +- log4j:log4j:jar:1.2.16:compile [INFO] | +- commons-lang:commons-lang:jar:2.6:compile [INFO] | +- commons-codec:commons-codec:jar:1.6:compile [INFO] | +- javax.validation:validation-api:jar:1.0.0.GA:compile [INFO] | +- org.hibernate:hibernate-validator:jar:4.2.0.Final:compile [INFO] | +- org.codehaus.jackson:jackson-core-asl:jar:1.9.5:compile [INFO] | +- org.codehaus.jackson:jackson-mapper-asl:jar:1.9.5:compile [INFO] | +- org.codehaus.jackson:jackson-jaxrs:jar:1.9.5:compile [INFO] | +- org.codehaus.jackson:jackson-xc:jar:1.9.5:compile [INFO] | \- com.fasterxml.jackson.dataformat:jackson-dataformat-xml:jar:2.2.3:compile [INFO] | +- com.fasterxml.jackson.core:jackson-core:jar:2.2.3:compile [INFO] | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.2.3:compile [INFO] | +- com.fasterxml.jackson.core:jackson-databind:jar:2.2.3:compile [INFO] | +- com.fasterxml.jackson.module:jackson-module-jaxb-annotations:jar:2.2.3:compile [INFO] | \- org.codehaus.woodstox:stax2-api:jar:3.1.1:compile [INFO] | \- javax.xml.stream:stax-api:jar:1.0-2:compile
?
剛才吹噓dependency:tree時(shí),我用到了“無處遁形”,其實(shí)有時(shí)你會(huì)發(fā)現(xiàn)簡單地用dependency:tree往往并不能查看到所有的傳遞依賴。不過如果你真的想要看所有的,必須得加一個(gè)-Dverbose參數(shù),這時(shí)就必定是最全的了。
全是全了,但顯示出來的東西太多,頭暈?zāi)垦?,有沒有好法呢?當(dāng)然有了,加上Dincludes或者Dexcludes說出你喜歡或討厭,dependency:tree就會(huì)幫你過濾出來:
引用
Dincludes=org.springframework:spring-tx
?
過濾串使用groupId:artifactId:version的方式進(jìn)行過濾,可以不寫全啦,如:
mvn dependency:tree -Dverbose -Dincludes=asm:asm
?
就會(huì)出來asm依賴包的分析信息:
[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ ridge-test --- [INFO] com.ridge:ridge-test:jar:1.0.2-SNAPSHOT [INFO] +- asm:asm:jar:3.2:compile [INFO] \- org.unitils:unitils-dbmaintainer:jar:3.3:compile [INFO] \- org.hibernate:hibernate:jar:3.2.5.ga:compile [INFO] +- cglib:cglib:jar:2.1_3:compile [INFO] | \- (asm:asm:jar:1.5.3:compile - omitted for conflict with 3.2) [INFO] \- (asm:asm:jar:1.5.3:compile - omitted for conflict with 3.2) [INFO] ------------------------------------------------------------------------
?
對(duì)asm有依賴有一個(gè)直接的依賴(asm:asm:jar:3.2)還有一個(gè)傳遞進(jìn)入的依賴(asm:asm:jar:1.5.3)
第二板斧:將不想要的傳遞依賴剪除掉
承上,假設(shè)我們不希望asm:asm:jar:1.5.3出現(xiàn),根據(jù)分析,我們知道它是經(jīng)由org.unitils:unitils-dbmaintainer:jar:3.3引入的,那么在pom.xml中找到這個(gè)依賴,做其它的調(diào)整:
? ??
<dependency> <groupId>org.unitils</groupId> <artifactId>unitils-dbmaintainer</artifactId> <version>${unitils.version}</version> <exclusions> <exclusion> <artifactId>dbunit</artifactId> <groupId>org.dbunit</groupId> </exclusion> <!-- 這個(gè)就是我們要加的片斷 --> <exclusion> <artifactId>asm</artifactId> <groupId>asm</groupId> </exclusion> </exclusions> </dependency>
?
再分析一下,你可以看到傳遞依賴沒有了:
[INFO] [INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ ridge-test --- [INFO] com.ridge:ridge-test:jar:1.0.2-SNAPSHOT [INFO] \- asm:asm:jar:3.2:compile [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS
?
第三板斧:查看運(yùn)行期類來源的JAR包
有時(shí),你以為解決了,但是偏偏還是報(bào)類包沖突(典型癥狀是java.lang.ClassNotFoundException或Method不兼容等異常),這時(shí)你可以設(shè)置一個(gè)斷點(diǎn),在斷點(diǎn)處通過下面這個(gè)我做的工具類來查看Class所來源的JAR包:
? ?
package com.ridge.util;</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.io.File; </span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.net.MalformedURLException; </span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.net.URL; </span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.security.CodeSource; </span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.security.ProtectionDomain; </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * </span><span style="color: rgba(128, 128, 128, 1)">@author</span><span style="color: rgba(0, 128, 0, 1)"> : chenxh * @date: 13-10-31 </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ClassLocationUtils { </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 獲取類所有的路徑 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> cls * </span><span style="color: rgba(128, 128, 128, 1)">@return</span> <span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> String where(<span style="color: rgba(0, 0, 255, 1)">final</span><span style="color: rgba(0, 0, 0, 1)"> Class cls) { </span><span style="color: rgba(0, 0, 255, 1)">if</span> (cls == <span style="color: rgba(0, 0, 255, 1)">null</span>)<span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> IllegalArgumentException("null input: cls"<span style="color: rgba(0, 0, 0, 1)">); URL result </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">final</span> String clsAsResource = cls.getName().replace('.', '/').concat(".class"<span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 0, 255, 1)">final</span> ProtectionDomain pd =<span style="color: rgba(0, 0, 0, 1)"> cls.getProtectionDomain(); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (pd != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">final</span> CodeSource cs =<span style="color: rgba(0, 0, 0, 1)"> pd.getCodeSource(); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (cs != <span style="color: rgba(0, 0, 255, 1)">null</span>) result =<span style="color: rgba(0, 0, 0, 1)"> cs.getLocation(); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (result != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">if</span> ("file"<span style="color: rgba(0, 0, 0, 1)">.equals(result.getProtocol())) { </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> { </span><span style="color: rgba(0, 0, 255, 1)">if</span> (result.toExternalForm().endsWith(".jar") ||<span style="color: rgba(0, 0, 0, 1)"> result.toExternalForm().endsWith(</span>".zip"<span style="color: rgba(0, 0, 0, 1)">)) result </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> URL("jar:"<span style="color: rgba(0, 0, 0, 1)">.concat(result.toExternalForm()) .concat(</span>"!/"<span style="color: rgba(0, 0, 0, 1)">).concat(clsAsResource)); </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> File(result.getFile()).isDirectory()) result </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> URL(result, clsAsResource); } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (MalformedURLException ignore) {} } } } </span><span style="color: rgba(0, 0, 255, 1)">if</span> (result == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">final</span> ClassLoader clsLoader =<span style="color: rgba(0, 0, 0, 1)"> cls.getClassLoader(); result </span>= clsLoader != <span style="color: rgba(0, 0, 255, 1)">null</span> ?<span style="color: rgba(0, 0, 0, 1)"> clsLoader.getResource(clsAsResource) : ClassLoader.getSystemResource(clsAsResource); } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result.toString(); } } </span></pre>
?
隨便寫一個(gè)測試,設(shè)置好斷點(diǎn),在執(zhí)行到斷點(diǎn)處按alt+F8動(dòng)態(tài)執(zhí)行代碼(intelij idea),假設(shè)我們輸入:
ClassLocationUtils.where(org.objectweb.asm.ClassVisitor.class)
?
即可馬上查出類對(duì)應(yīng)的JAR了:
這就是org.objectweb.asm.ClassVisitor類在運(yùn)行期對(duì)應(yīng)的JAR包,如果這個(gè)JAR包版本不是你期望你,就說明是你的IDE緩存造成的,這時(shí)建議你Reimport一下maven列表就可以了,如下所示(idea):
Reimport一下,IDE會(huì)強(qiáng)制根據(jù)新的pom.xml設(shè)置重新分析并加載依賴類包,以得到和pom.xml設(shè)置相同的依賴。(這一步非常重要哦,經(jīng)常項(xiàng)目組pom.xml是相同的,但是就是有些人可以運(yùn)行,有些人不能運(yùn)行,俗稱人品問題,其實(shí)都是IDE的緩存造成的了
idea清除緩存,為了提高效率不建議采用reimport重新起開啟項(xiàng)目的方式,建議采用idea自帶的功能,File->Invalidate Caches 功能直接完成清除idea cache
?
?
?
?
?
?
三、另一個(gè)問題,log沖突:項(xiàng)目中出現(xiàn)的問題如下:
?
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.apache.log4j.Log4jLoggerFactory
?
后經(jīng)網(wǎng)上搜索加邊上大牛指點(diǎn)發(fā)現(xiàn):
log4j-over-slf4j.jar 和 slf4j-log4j12.jar 在同一個(gè)classpath下就會(huì)出現(xiàn)這個(gè)錯(cuò)誤。
解決方法:?
將slf4j-log4j12.jar從相關(guān)的jar中排除
但是查看maven項(xiàng)目中的pom文件,自己并沒有配置這個(gè)jar的依賴,猜測是maven加載其他jar引入的依賴包。
打開pom.xml文件,在Dependency Hierarchy(依賴列表)中查看jar包的依賴層次關(guān)系。
?
在過濾欄中輸入log4j,右側(cè)出現(xiàn)了log4j相關(guān)包的依賴結(jié)構(gòu),左側(cè)則是pom.xml全部依賴包的列表展示。
直接在右側(cè)選中zookeeper底下的slf4j的jar包,右鍵選擇Exclude,然后保存pom.xml。這樣在加載zookeeper的jar包時(shí)就不會(huì)再加載slf4j的jar包。
修改后對(duì)應(yīng)的dependency文件如下:
?
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.6</version> <exclusions> <exclusion> <artifactId>slf4j-log4j12</artifactId> <groupId>org.slf4j</groupId> </exclusion> </exclusions> </dependency>
?
這樣就能通過filter過濾快速找到對(duì)應(yīng)jar,并知道他的依賴關(guān)系,快速解決項(xiàng)目中的jar包沖突問題。?
參考:Maven類包沖突終極解決方案
參考:利用maven工具解決jar包沖突問題或重復(fù)加載問題
參考:Maven 解決jar包沖突的原理文章來源:http://www.zghlxwxcb.cn/news/detail-438163.html
本文來自博客園,作者:aspirant,轉(zhuǎn)載請(qǐng)注明原文鏈接:https://www.cnblogs.com/aspirant/p/8532962.html文章來源地址http://www.zghlxwxcb.cn/news/detail-438163.html
舉例
A依賴于B及C,而B又依賴于X、Y,而C依賴于X、M,則A除引B及C的依賴包下,還會(huì)引入X,Y,M的依賴包(一般情況下了,Maven可通過<scope>等若干種方式控制傳遞依賴)。
這里有一個(gè)需要特別注意的,即B和C同時(shí)依賴于X,假設(shè)B依賴于X的1.0版本,而C依賴于X的2.0版本,A究竟依賴于X的1.0還是2.0版本呢?
這就看Classloader的加載順序了,假設(shè)Classloader先加載X_1.0,而它就不會(huì)再加載X_2.0了,如果A恰恰希望使用X_2.0呢,血案就這樣不期而遇了。
?
比如 A 依賴 版本為2.0 的 C ,B 依賴 版本為3.0的 C。在你的pom中,你同時(shí)依賴了 A 和 B ,這時(shí)就會(huì)產(chǎn)生沖突。這時(shí)候你就要判斷,哪個(gè)版本能同時(shí)讓A和B工作(如果可以的話),然后排除掉另一個(gè)就行了。我通常都是排除掉較低的版本。?
<dependencies> <dependency> <groupId>A</groupId> <artifactId>A</artifactId> <version>xxx</version> <exclusions> <exclusion> <groupId>C</groupId> <artifactId>C</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>B</groupId> <artifactId>B</artifactId> </dependency> </dependencies>
?
理包依賴是 Maven 核心功能之一,下面通過如何引入 jar 包;如何解析 jar 包依賴;包沖突是如何產(chǎn)生;如何解決包沖突;依賴管理解決什么問題;什么是依賴范圍;使用包依賴的最佳實(shí)踐等 6 個(gè)問題來介紹。
如何引入 jar 包
在代碼開發(fā)時(shí),如果需要使用第三方 jar 包提供的類庫,那么需要在 pom.xml 加入該 jar 包依賴。 例如:使用 zookeeper client
<dependencies> <!-- https://mvnrepository.com/artifact/org.apache.hadoop/zookeeper --> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>zookeeper</artifactId> <version>3.3.1</version> </dependency> </dependencies>
?
Maven 如何解析 jar 包依賴——傳遞依賴
如上所述,在 pom.xml 中引入 zookeeper jar 包依賴,當(dāng) Maven 解析該依賴時(shí),需要引入的 jar 包不僅僅只有 zookeeper,還會(huì)有 zookeeper 內(nèi)部依賴的 jar 包,還會(huì)有 zookeeper 內(nèi)部依賴的 jar 包依賴的 jar 包......,依賴關(guān)系不斷傳遞,直至沒有依賴。
例如:上述 pom.xml 引入 zookeeper 依賴,實(shí)際引入的 jar 包有
包沖突如何產(chǎn)生?
舉個(gè)?:假設(shè) A->B->C->D1, E->F->D2,D1,D2 分別為 D 的不同版本。
如果 pom.xml 文件中引入了 A 和 E 之后,按照 Maven 傳遞依賴原則,工程內(nèi)需要引入的實(shí)際 Jar 包將會(huì)有:A B C D1 和 E F D2,因此 D1,D2 將會(huì)產(chǎn)生包沖突。
如何解決包沖突
Maven 解析 pom.xml 文件時(shí),同一個(gè) jar 包只會(huì)保留一個(gè),這樣有效的避免因引入兩個(gè) jar 包導(dǎo)致的工程運(yùn)行不穩(wěn)定性。
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)先
如果路徑一樣的話,舉個(gè)?: A->B->C1, E->F->C2 ,兩個(gè)依賴路徑長度都是 2,那么就選擇最先聲明。
移除依賴
如果我們不想通過 A->B->->D1 引入 D1 的話,那么我們?cè)诼暶饕?A 的時(shí)候?qū)?D1 排除掉,這樣也避免了包沖突。
舉個(gè)?:將 zookeeper 的 jline 依賴排除 用exclusions標(biāo)簽
<dependency> <groupId>org.apache.hadoop</groupId> <artifactId>zookeeper</artifactId> <version>3.3.1</version> <exclusions> <exclusion> <groupId>jline</groupId> <artifactId>jline</artifactId> </exclusion> </exclusions> </dependency>
?
檢測包沖突工具
mvn dependency:help
mvn dependency:analyze
mvn dependency:tree
mvn dependency:tree -Dverbose
詳細(xì)參考:mvn dependency
mvn dependency:tree
依賴管理解決什么問題
當(dāng)同一個(gè)工程內(nèi)有多個(gè)模塊時(shí),并且要求多個(gè)模塊使用某個(gè) jar 包的相同版本,為了方便統(tǒng)一版本號(hào),升級(jí)版本號(hào),需要提取出一個(gè)父親模塊來管理子模塊共同依賴的 jar 包版本。
舉個(gè)?:有兩個(gè)模塊 projectA, projectB,它們的依賴分別如下所示:
projectA:
<project>
...
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-a</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>group-c</groupId>
<artifactId>excluded-artifact</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>bar</type>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
?
projectB:
<project>
...
<dependencies>
<dependency>
<groupId>group-c</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>bar</type>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
?
projectA 和 projectB 共同依賴了 group-a/artifact-b/1.0,提取公共依賴,生成 parent, parent 依賴如下:
<project>
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>bar</type>
<scope>runtime</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
?
則 projectA 和 projectB 均不需要指定 group-a/artifact-b 的 version 信息,未來升級(jí) version 信息時(shí),只需要在 parent 內(nèi)部指定。
projectA:
<project>
...
<parent>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
</parent>
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-a</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>group-c</groupId>
<artifactId>excluded-artifact</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
</dependency>
</dependencies>
</project>
?
projectB:
<project>
...
<dependencies>
<dependency>
<groupId>group-c</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
</dependency>
</dependencies>
</project>
?
依賴范圍
如果不顯示執(zhí)行 <scope> 屬性時(shí),默認(rèn) <scope>compile</scope>。
scope 有哪些屬性:compile, provided, runtime, test, system 等。
詳細(xì)參考:依賴范圍
最佳實(shí)踐
(1)項(xiàng)目中源代碼使用的 jar 包一定在 pom.xml 中顯示引用。
(2)經(jīng)常 check 一下包沖突,檢查是否需要處理。
(3)當(dāng)使用多個(gè)模塊時(shí),parent 一定要使用包管理模塊來規(guī)范 Jar 包版本,而不是包依賴模塊直接引入依賴。 dependencyManagement vs dependencies
?
?
?
?第二個(gè)問題:
第一板斧:找到傳遞依賴的鬼出在哪里?
dependency:tree是把照妖照,pom.xml用它照照,所有傳遞性依賴都將無處遁形,并且會(huì)以層級(jí)樹方式展現(xiàn),非常直觀。
以下就是執(zhí)行dependency:tree后的一個(gè)輸出:
引用
[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ euler-foundation --- [INFO] com.hsit:euler-foundation:jar:0.9.0.1-SNAPSHOT [INFO] +- com.rop:rop:jar:1.0.1:compile [INFO] | +- org.slf4j:slf4j-api:jar:1.7.5:compile [INFO] | +- org.slf4j:slf4j-log4j12:jar:1.7.5:compile [INFO] | +- log4j:log4j:jar:1.2.16:compile [INFO] | +- commons-lang:commons-lang:jar:2.6:compile [INFO] | +- commons-codec:commons-codec:jar:1.6:compile [INFO] | +- javax.validation:validation-api:jar:1.0.0.GA:compile [INFO] | +- org.hibernate:hibernate-validator:jar:4.2.0.Final:compile [INFO] | +- org.codehaus.jackson:jackson-core-asl:jar:1.9.5:compile [INFO] | +- org.codehaus.jackson:jackson-mapper-asl:jar:1.9.5:compile [INFO] | +- org.codehaus.jackson:jackson-jaxrs:jar:1.9.5:compile [INFO] | +- org.codehaus.jackson:jackson-xc:jar:1.9.5:compile [INFO] | \- com.fasterxml.jackson.dataformat:jackson-dataformat-xml:jar:2.2.3:compile [INFO] | +- com.fasterxml.jackson.core:jackson-core:jar:2.2.3:compile [INFO] | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.2.3:compile [INFO] | +- com.fasterxml.jackson.core:jackson-databind:jar:2.2.3:compile [INFO] | +- com.fasterxml.jackson.module:jackson-module-jaxb-annotations:jar:2.2.3:compile [INFO] | \- org.codehaus.woodstox:stax2-api:jar:3.1.1:compile [INFO] | \- javax.xml.stream:stax-api:jar:1.0-2:compile
?
剛才吹噓dependency:tree時(shí),我用到了“無處遁形”,其實(shí)有時(shí)你會(huì)發(fā)現(xiàn)簡單地用dependency:tree往往并不能查看到所有的傳遞依賴。不過如果你真的想要看所有的,必須得加一個(gè)-Dverbose參數(shù),這時(shí)就必定是最全的了。
全是全了,但顯示出來的東西太多,頭暈?zāi)垦#袥]有好法呢?當(dāng)然有了,加上Dincludes或者Dexcludes說出你喜歡或討厭,dependency:tree就會(huì)幫你過濾出來:
引用
Dincludes=org.springframework:spring-tx
?
過濾串使用groupId:artifactId:version的方式進(jìn)行過濾,可以不寫全啦,如:
mvn dependency:tree -Dverbose -Dincludes=asm:asm
?
就會(huì)出來asm依賴包的分析信息:
[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ ridge-test --- [INFO] com.ridge:ridge-test:jar:1.0.2-SNAPSHOT [INFO] +- asm:asm:jar:3.2:compile [INFO] \- org.unitils:unitils-dbmaintainer:jar:3.3:compile [INFO] \- org.hibernate:hibernate:jar:3.2.5.ga:compile [INFO] +- cglib:cglib:jar:2.1_3:compile [INFO] | \- (asm:asm:jar:1.5.3:compile - omitted for conflict with 3.2) [INFO] \- (asm:asm:jar:1.5.3:compile - omitted for conflict with 3.2) [INFO] ------------------------------------------------------------------------
?
對(duì)asm有依賴有一個(gè)直接的依賴(asm:asm:jar:3.2)還有一個(gè)傳遞進(jìn)入的依賴(asm:asm:jar:1.5.3)
第二板斧:將不想要的傳遞依賴剪除掉
承上,假設(shè)我們不希望asm:asm:jar:1.5.3出現(xiàn),根據(jù)分析,我們知道它是經(jīng)由org.unitils:unitils-dbmaintainer:jar:3.3引入的,那么在pom.xml中找到這個(gè)依賴,做其它的調(diào)整:
? ??
<dependency> <groupId>org.unitils</groupId> <artifactId>unitils-dbmaintainer</artifactId> <version>${unitils.version}</version> <exclusions> <exclusion> <artifactId>dbunit</artifactId> <groupId>org.dbunit</groupId> </exclusion> <!-- 這個(gè)就是我們要加的片斷 --> <exclusion> <artifactId>asm</artifactId> <groupId>asm</groupId> </exclusion> </exclusions> </dependency>
?
再分析一下,你可以看到傳遞依賴沒有了:
[INFO] [INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ ridge-test --- [INFO] com.ridge:ridge-test:jar:1.0.2-SNAPSHOT [INFO] \- asm:asm:jar:3.2:compile [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS
?
第三板斧:查看運(yùn)行期類來源的JAR包
有時(shí),你以為解決了,但是偏偏還是報(bào)類包沖突(典型癥狀是java.lang.ClassNotFoundException或Method不兼容等異常),這時(shí)你可以設(shè)置一個(gè)斷點(diǎn),在斷點(diǎn)處通過下面這個(gè)我做的工具類來查看Class所來源的JAR包:
? ?
package com.ridge.util;</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.io.File; </span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.net.MalformedURLException; </span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.net.URL; </span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.security.CodeSource; </span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.security.ProtectionDomain; </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * </span><span style="color: rgba(128, 128, 128, 1)">@author</span><span style="color: rgba(0, 128, 0, 1)"> : chenxh * @date: 13-10-31 </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ClassLocationUtils { </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 獲取類所有的路徑 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> cls * </span><span style="color: rgba(128, 128, 128, 1)">@return</span> <span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> String where(<span style="color: rgba(0, 0, 255, 1)">final</span><span style="color: rgba(0, 0, 0, 1)"> Class cls) { </span><span style="color: rgba(0, 0, 255, 1)">if</span> (cls == <span style="color: rgba(0, 0, 255, 1)">null</span>)<span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> IllegalArgumentException("null input: cls"<span style="color: rgba(0, 0, 0, 1)">); URL result </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">final</span> String clsAsResource = cls.getName().replace('.', '/').concat(".class"<span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 0, 255, 1)">final</span> ProtectionDomain pd =<span style="color: rgba(0, 0, 0, 1)"> cls.getProtectionDomain(); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (pd != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">final</span> CodeSource cs =<span style="color: rgba(0, 0, 0, 1)"> pd.getCodeSource(); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (cs != <span style="color: rgba(0, 0, 255, 1)">null</span>) result =<span style="color: rgba(0, 0, 0, 1)"> cs.getLocation(); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (result != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">if</span> ("file"<span style="color: rgba(0, 0, 0, 1)">.equals(result.getProtocol())) { </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> { </span><span style="color: rgba(0, 0, 255, 1)">if</span> (result.toExternalForm().endsWith(".jar") ||<span style="color: rgba(0, 0, 0, 1)"> result.toExternalForm().endsWith(</span>".zip"<span style="color: rgba(0, 0, 0, 1)">)) result </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> URL("jar:"<span style="color: rgba(0, 0, 0, 1)">.concat(result.toExternalForm()) .concat(</span>"!/"<span style="color: rgba(0, 0, 0, 1)">).concat(clsAsResource)); </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> File(result.getFile()).isDirectory()) result </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> URL(result, clsAsResource); } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (MalformedURLException ignore) {} } } } </span><span style="color: rgba(0, 0, 255, 1)">if</span> (result == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">final</span> ClassLoader clsLoader =<span style="color: rgba(0, 0, 0, 1)"> cls.getClassLoader(); result </span>= clsLoader != <span style="color: rgba(0, 0, 255, 1)">null</span> ?<span style="color: rgba(0, 0, 0, 1)"> clsLoader.getResource(clsAsResource) : ClassLoader.getSystemResource(clsAsResource); } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result.toString(); } } </span></pre>
?
隨便寫一個(gè)測試,設(shè)置好斷點(diǎn),在執(zhí)行到斷點(diǎn)處按alt+F8動(dòng)態(tài)執(zhí)行代碼(intelij idea),假設(shè)我們輸入:
ClassLocationUtils.where(org.objectweb.asm.ClassVisitor.class)
?
即可馬上查出類對(duì)應(yīng)的JAR了:
這就是org.objectweb.asm.ClassVisitor類在運(yùn)行期對(duì)應(yīng)的JAR包,如果這個(gè)JAR包版本不是你期望你,就說明是你的IDE緩存造成的,這時(shí)建議你Reimport一下maven列表就可以了,如下所示(idea):
Reimport一下,IDE會(huì)強(qiáng)制根據(jù)新的pom.xml設(shè)置重新分析并加載依賴類包,以得到和pom.xml設(shè)置相同的依賴。(這一步非常重要哦,經(jīng)常項(xiàng)目組pom.xml是相同的,但是就是有些人可以運(yùn)行,有些人不能運(yùn)行,俗稱人品問題,其實(shí)都是IDE的緩存造成的了
idea清除緩存,為了提高效率不建議采用reimport重新起開啟項(xiàng)目的方式,建議采用idea自帶的功能,File->Invalidate Caches 功能直接完成清除idea cache
?
?
?
?
?
?
三、另一個(gè)問題,log沖突:項(xiàng)目中出現(xiàn)的問題如下:
?
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.apache.log4j.Log4jLoggerFactory
?
后經(jīng)網(wǎng)上搜索加邊上大牛指點(diǎn)發(fā)現(xiàn):
log4j-over-slf4j.jar 和 slf4j-log4j12.jar 在同一個(gè)classpath下就會(huì)出現(xiàn)這個(gè)錯(cuò)誤。
解決方法:?
將slf4j-log4j12.jar從相關(guān)的jar中排除
但是查看maven項(xiàng)目中的pom文件,自己并沒有配置這個(gè)jar的依賴,猜測是maven加載其他jar引入的依賴包。
打開pom.xml文件,在Dependency Hierarchy(依賴列表)中查看jar包的依賴層次關(guān)系。
?
在過濾欄中輸入log4j,右側(cè)出現(xiàn)了log4j相關(guān)包的依賴結(jié)構(gòu),左側(cè)則是pom.xml全部依賴包的列表展示。
直接在右側(cè)選中zookeeper底下的slf4j的jar包,右鍵選擇Exclude,然后保存pom.xml。這樣在加載zookeeper的jar包時(shí)就不會(huì)再加載slf4j的jar包。
修改后對(duì)應(yīng)的dependency文件如下:
?
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.6</version> <exclusions> <exclusion> <artifactId>slf4j-log4j12</artifactId> <groupId>org.slf4j</groupId> </exclusion> </exclusions> </dependency>
?
這樣就能通過filter過濾快速找到對(duì)應(yīng)jar,并知道他的依賴關(guān)系,快速解決項(xiàng)目中的jar包沖突問題。?
參考:Maven類包沖突終極解決方案
參考:利用maven工具解決jar包沖突問題或重復(fù)加載問題
參考:Maven 解決jar包沖突的原理
本文來自博客園,作者:aspirant,轉(zhuǎn)載請(qǐng)注明原文鏈接:https://www.cnblogs.com/aspirant/p/8532962.html
到了這里,關(guān)于Maven 3-Maven依賴版本沖突的分析及解決小結(jié)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!