jar依賴沖突解決實踐
前言
隨着功能的增多,各種中間件的引入。應用以來的各種jar的規模極具膨脹,出現jar沖突和Class沖突的問題層出不窮,讓人不勝其擾。本文針對沖突,提供一個排查和定位問題的最佳實踐。實踐中盡量不借助第三方工具,而使用maven或者Linux的自帶命令行。
Maven構建的應用的jar沖突
目前最為最流行的項目構建和管理工具,在目前的互聯網應用中被廣泛使用。maven框架很大的一個便利就是對於jar的依賴管理,它自然提供了一些工具幫助開發者進行依賴分析。maven存在坐標的概念。groupId,artifactId,version三個維度定位到一個唯一的jar。
1 2 3 4 5 6 7 |
<dependency> <groupId>com.taobao.diamond</groupId> <artifactId>diamond-client</artifactId> <version>3.6.0</version> <type>jar</type> <scope>compile</scope> </dependency> |
對於版本,有一個很寬泛的范圍
[3.6.0,4.0.0) 要求的依賴版本>=3.6.0且<4.0.0
[,3.6.0] 要求的依賴版本<=3.6.0
對於應用來講,還是固定一個版本為好,誇版本有太多不可預知的情況存在。
靜態代碼檢查
通過mvn dependency:tree 命令查看依賴樹
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
[INFO] \- com.alibaba.china.shared:credit_shared.ruleengine.biz:jar:1.0-SNAPSHOT:compile [INFO] +- com.alibaba.china.shared:credit_shared.ruleengine.api:jar:1.0-SNAPSHOT:compile [INFO] +- com.alibaba.china.shared:credit_shared.ruleengine.dal:jar:1.0-SNAPSHOT:compile [INFO] +- org.antlr:antlr:jar:3.3:compile [INFO] +- org.antlr:antlr-runtime:jar:3.3:compile [INFO] | \- org.antlr:stringtemplate:jar:3.2.1:compile [INFO] +- org.mvel:mvel2:jar:2.1.3.Final:compile [INFO] +- org.drools:knowledge-api:jar:5.5.0.Final:compile [INFO] +- org.drools:drools-core:jar:5.5.0.Final:compile [INFO] | \- org.drools:knowledge-internal-api:jar:5.5.0.Final:compile [INFO] \- org.drools:drools-compiler:jar:5.5.0.Final:compile [INFO] +- org.antlr:antlr:jar:2.7.7:compile [INFO] +- org.eclipse.jdt.core.compiler:ecj:jar:3.5.1:compile [INFO] \- com.thoughtworks.xstream:xstream:jar:1.4.1:compile [INFO] +- xmlpull:xmlpull:jar:1.1.3.1:compile [INFO] \- xpp3:xpp3_min:jar:1.1.4c:compile |
通過靜態代碼掃描的方式,能分析出來jar之間的依賴關系。舉例credit_shared.ruleengine.biz-1.0-SNAPSHOT.jar依賴了antlr-3.3.jar。org.antlr:antlr-3.3.jar在maven中的坐標是
1 2 3 4 5 6 7 |
<dependency> <groupId>org.antlr</groupId> <artifactId>antlr</artifactId> <version>3.3</version> <type>jar</type> <scope>compile</scope> </dependency> |
依賴仲裁
從上面的依賴樹,出現了另外一個版本的jar——org.antlr:antlr:jar:2.7.7:compile,這就出現了依賴仲裁的問題。
maven 2.2.1版本仲裁規則:
- 按照項目總POM的DependencyManager版本聲明進行仲裁(覆蓋),但無警告
- 如無仲裁聲明,則按照依賴最短路徑確定版本
- 若相同路徑,有嚴格區間限定的版本優先
- 若相同路徑,無版本區間,則按照先入為主原則
如要解決沖突問題,很多時候都用到exclusions,如A->B->D(v1),A->C-D(v2),要指定A->D(v1),則需要在聲明C的依賴時候通過exclusions列表排除掉對D(v2)的依賴。
要更好理解依賴仲裁,需要了解以下附帶知識。
maven classpath
maven中有三種classpath:
- 編譯classpath:編譯項目代碼,依賴的jar會被引入到classpath
- 測試classpath:編譯和執行測試部分代碼,如單元測試,集成測試,依賴會被引入到classpath
- 運行classpath:實際運行代碼的時候,依賴的jar會被引入到classpath
scope:依賴范圍
scope就是為了解決jar在classpath中的可見性。scope有以下幾個可選項
compile:默認值,對編譯classpath、測試classpath、運行classpath都有效,在三個階段都需要指定的jar
provided:編譯和測試可用,不會被傳遞依賴,不會被打包。例:依賴於web容器中的提供的一個jar包,在編譯的時候需要加入依賴(web容器還沒有介入),運行的時候由web容器來提供。如servlet-api。
test:執行單元測試時可用,不會被打包,不會被傳遞依賴
runtime:運行和測試時需要,但編譯時不需要。最典型的例子是JDBC的驅動,編譯時只需要提供驅動的API即可,在運行和測試階段,需要加載到具體的驅動實現。
system:跟provided一致,顯示制定依賴路徑,一般是指定了本地的倉庫之外的類庫文件。可能造成不可依賴性,不推薦使用。
傳遞性依賴
A->B,B->C,則A->C。這是傳遞性依賴。依賴是有范圍的,A->B,B->C的依賴范圍決定了A->C的依賴范圍。
A->C | compile | provided | test | runtime | |
---|---|---|---|---|---|
compile | compile | runtime | |||
provided | provided | provided | provided | ||
test | test | test | |||
runtime | runtime | runtime |
可選依賴
1 2 3 4 5 6 7 8 |
<dependency> <groupId>org.antlr</groupId> <artifactId>antlr</artifactId> <version>3.3</version> <type>jar</type> <scope>compile</scope> <optional>true</optional> </dependency> |
A->B,B->C(可選),B->D(可選),則A不會通過傳遞依賴到C或者D。
非Maven項目或者不同坐標的jar出現Class沖突,
上面介紹的是jar相同而版本不同,如antlr-3.3.jar,antlr-3.2.jar類似情況的沖突解決方案。這種情況一般出現在中間件升級。下面介紹坐標不同,如antlr-old-3.3.jar、antlr-new.jar,而jar中包含了類路徑完全相同的類的情況。
出現這種情況,一般的異常提示都是“XXX類has no such method XXXX”之類的。這些異常提示基本可以定位成Class不是你要的Class,只不過Class的全路徑是相同的,最大的可能也就是ClassLoader加載了另外一個jar的同名類。所以,首先要排查到該類是從哪個具體jar中來的。JVM提供了這樣的功能,查看加載類的情況
1 |
java -verbose:class |
類加載情況,如SubscriptionInfo類加載自file:/home/zhao/web-deploy/jettyserver/tmp/jetty-0.0.0.0-34200-root.war--any-/webinf/WEB-INF/lib/pc2.common-1.2.5.jar
1 2 |
[Loaded com.alibaba.pc2.common.remote.subscription.SubscriptionInfo from file:/home/zhao/web-deploy/jetty_server/tmp/jetty-0.0.0.0-34200-root.war-_-any-/webinf/WEB-INF/lib/pc2.common-1.2.5.jar] [Loaded com.alibaba.pc2.common.domain.productpackage.PackageInfo from file:/home/zhao/web-deploy/jetty_server/tmp/jetty-0.0.0.0-34200-root.war-_-any-/webinf/WEB-INF/lib/pc2.common-1.2.5.jar] |
而同樣在pmap pid可以進程的內存鏡像。 jps -v查看pid,再通過pmap pid > map.txt,從map.txt中查到
1 |
00007fa4ae174000 20K r-xs- /home/zhao/web-deploy/jetty_server/tmp/jetty-0.0.0.0-34200-root.war-_-any-/webinf/WEB-INF/lib/pc2.common-1.2.5.jar |
得到確認后,如果是maven工程,則通過依賴樹查詢到是具體哪個jar依賴了這個錯誤引用,在pom文件中exclusions掉該jar即可。
如果是非maven工程,則通過其他方式把錯誤引用排除掉即可。