【Maven依賴衝突】Maven jar包衝突問題排查及解決方案
前言
寫這篇文章的初衷是因為今天在使用mvn dependency:tree命令時,突然想起一年前面試阿里的一道面試題。面試題是說假設線上發生JAR包衝突,應該怎麼排查?我那時候的回答是IDEA有個Maven Helper的外掛,可以幫忙分析依賴衝突,然後還有一種辦法是如果一個類import的時候提示兩個地方可匯入,那就說明有衝突。現在回頭想想確實太不專業了,以下是一次JAR包衝突的一個比較正規的流程,是通過整理幾篇部落格後總結的希望對大家也有幫助,如果有錯誤的地方也歡迎指出
關於我:http://huangth.com
GitHub地址:https://github.com/RobertoHuang
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排查衝突
mvn dependency:tree -Dverbose > tree.txt 寫到檔案中
[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)
1
即可馬上找到對應的JAR,如果這個JAR不是你期望的就說明是IDE快取造成的,清除快取後重試即可
原文:https://blog.csdn.net/RobertoHuang/article/details/81778181?utm_source=copy