Maven JAR包問題排查及解決方案
阿新 • • 發佈:2018-11-09
前言
寫這篇文章的初衷是因為今天在使用mvn dependency:tree
命令時,突然想起一年前面試阿里的一道面試題。面試題是說假設線上發生JAR
包衝突,應該怎麼排查?我那時候的回答是IDEA
有個Maven Helper
的外掛,可以幫忙分析依賴衝突,然後還有一種辦法是如果一個類import
的時候提示兩個地方可匯入,那就說明有衝突。現在回頭想想確實太不專業了,以下是一次JAR
包衝突的一個比較正規的流程,是通過整理幾篇部落格後總結的希望對大家也有幫助,如果有錯誤的地方也歡迎指出
-
JAR
衝突產生的原因Pom.xml / \ B C / \ / \ X Y X M
在以上依賴關係中專案除了會引入B、C還會引入X、Y、M的依賴包,但是如果B依賴的X版本會1.0而C依賴的X版本為2.0時,那最後專案使用的到底是X的1.0版本還是2.0版本就無法確定了。這是就要看
ClassLoader
的載入順序,假設ClassLoader
先載入1.0版本那就不會載入2.0版本,反之同理 -
使用
mvn -Dverbose dependency:tree
排查衝突[INFO] +- org.apache.tomcat:tomcat-servlet-api:jar:7.0.70:compile [INFO] +- org.apache.tomcat:tomcat-jsp-api:jar:7.0.70:compile [INFO] | +- org.apache.tomcat:tomcat-el-api:jar:7.0.70:compile [INFO] | \- (org.apache.tomcat:tomcat-servlet-api:jar:7.0.70:compile - omitted for duplicate) [INFO] +- net.sf.jasperreports:jasperreports:jar:5.6.0:compile [INFO] | +- (commons-beanutils:commons-beanutils:jar:1.8.0:compile - omitted for conflict with 1.8.3) [INFO] | +- commons-collections:commons-collections:jar:3.2.1:compile [INFO] | +- commons-digester:commons-digester:jar:2.1:compile [INFO] | | +- (commons-beanutils:commons-beanutils:jar:1.8.3:compile - omitted for duplicate) [INFO] | | \- (commons-logging:commons-logging:jar:1.1.1:compile - omitted for duplicate)
遞迴依賴的關係列的算是比較清楚了,每行都是一個jar包,根據縮排可以看到依賴的關係
- 最後寫著
compile
的就是編譯成功的 - 最後寫著
omitted for duplicate
的就是有JAR
包被重複依賴了,但是JAR
包的版本是一樣的 - 最後寫著
omitted for conflict with xx
的,說明和別的JAR
包版本衝突了,該行的JAR
包不會被引入
該命令可配合-Dincludes和-Dexcludes進行使用,只輸出自己感興趣/不感興趣的JAR 引數格式為:[groupId]:[artifactId]:[type]:[version] 每個部分(冒號分割的部分)是支援*萬用字元的,如果要指定多個格式則可以用,分割,如: mvn dependency:tree -Dincludes=javax.servlet,org.apache.*
- 最後寫著
-
解決衝突,使用
exclusion
標籤將衝突的JAR
排除<dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.8.3.2</version> <exclusions> <exclusion> <artifactId>guava</artifactId> <groupId>com.google.guava</groupId> </exclusion> <exclusion> <artifactId>spring</artifactId> <groupId>org.springframework</groupId> </exclusion> </exclusions> </dependency>
解決了衝突後的樹(解決衝突的策略是:就近原則,即離根近的依賴被採納)
-
檢視執行期類來源的JAR包
有時你以為解決了但是偏偏還是報類包衝突,典型症狀是
java.lang.ClassNotFoundException
或Method
不相容等異常,這時你可以設定一個斷點,在斷點處通過下面這個工具類來檢視Class
所來源的JAR
包public class ClassLocationUtils { public static String where(final Class clazz) { if (clazz == null) { throw new IllegalArgumentException("null input: cls"); } URL result = null; final String clazzAsResource = clazz.getName().replace('.', '/').concat(".class"); final ProtectionDomain protectionDomain = clazz.getProtectionDomain(); if (protectionDomain != null) { final CodeSource codeSource = protectionDomain.getCodeSource(); if (codeSource != null) result = codeSource.getLocation(); if (result != null) { if ("file".equals(result.getProtocol())) { try { if (result.toExternalForm().endsWith(".jar") || result.toExternalForm().endsWith(".zip")) { result = new URL("jar:".concat(result.toExternalForm()).concat("!/").concat(clazzAsResource)); } else if (new File(result.getFile()).isDirectory()) { result = new URL(result, clazzAsResource); } } catch (MalformedURLException ignore) { } } } } if (result == null) { final ClassLoader clsLoader = clazz.getClassLoader(); result = clsLoader != null ? clsLoader.getResource(clazzAsResource) : ClassLoader.getSystemResource(clazzAsResource); } return result.toString(); } }
然後隨便寫一個測試設定好斷點,在執行到斷點出使用
ALT+F8
動態執行程式碼,如ClassLocationUtils.where(Logger.class)
即可馬上找到對應的
JAR
,如果這個JAR
不是你期望的就說明是IDE
快取造成的,清除快取後重試即可