举例
A依赖于B及C,而B又依赖于X、Y,而C依赖于X、M,则A除引B及C的依赖包下,还会引入X,Y,M的依赖包(一般情况下了,Maven可通过等若干种方式控制传递依赖)。
这里有一个需要特别注意的,即B和C同时依赖于X,假设B依赖于X的1.0版本,而C依赖于X的2.0版本,A究竟依赖于X的1.0还是2.0版本呢?
这就看Classloader的加载顺序了,假设Classloader先加载X_1.0,而它就不会再加载X_2.0了,如果A恰恰希望使用X_2.0呢,血案就这样不期而遇了。
比如 A 依赖 版本为2.0 的 C ,B 依赖 版本为3.0的 C。在你的pom中,你同时依赖了 A 和 B ,这时就会产生冲突。这时候你就要判断,哪个版本能同时让A和B工作(如果可以的话),然后排除掉另一个就行了。我通常都是排除掉较低的版本。
A
A
xxx
C
C
B
B
理包依赖是 Maven 核心功能之一,下面通过如何引入 jar 包;如何解析 jar 包依赖;包冲突是如何产生;如何解决包冲突;依赖管理解决什么问题;什么是依赖范围;使用包依赖的最佳实践等 6 个问题来介绍。
如何引入 jar 包
在代码开发时,如果需要使用第三方 jar 包提供的类库,那么需要在 pom.xml 加入该 jar 包依赖。 例如:使用 zookeeper client
org.apache.hadoop
zookeeper
3.3.1
Maven 如何解析 jar 包依赖——传递依赖
如上所述,在 pom.xml 中引入 zookeeper jar 包依赖,当 Maven 解析该依赖时,需要引入的 jar 包不仅仅只有 zookeeper,还会有 zookeeper 内部依赖的 jar 包,还会有 zookeeper 内部依赖的 jar 包依赖的 jar 包......,依赖关系不断传递,直至没有依赖。
例如:上述 pom.xml 引入 zookeeper 依赖,实际引入的 jar 包有
包冲突如何产生?
举个�:假设 A->B->C->D1, E->F->D2,D1,D2 分别为 D 的不同版本。
如果 pom.xml 文件中引入了 A 和 E 之后,按照 Maven 传递依赖原则,工程内需要引入的实际 Jar 包将会有:A B C D1 和 E F D2,因此 D1,D2 将会产生包冲突。
如何解决包冲突
Maven 解析 pom.xml 文件时,同一个 jar 包只会保留一个,这样有效的避免因引入两个 jar 包导致的工程运行不稳定性。
Maven 默认处理策略
最短路径优先
Maven 面对 D1 和 D2 时,会默认选择最短路径的那个 jar 包,即 D2。E->F->D2 比 A->B->C->D1 路径短 1。
最先声明优先
如果路径一样的话,举个�: A->B->C1, E->F->C2 ,两个依赖路径长度都是 2,那么就选择最先声明。
移除依赖
如果我们不想通过 A->B->->D1 引入 D1 的话,那么我们在声明引入 A 的时候将 D1 排除掉,这样也避免了包冲突。
举个�:将 zookeeper 的 jline 依赖排除 用exclusions标签
org.apache.hadoop
zookeeper
3.3.1
jline
jline
检测包冲突工具
mvn dependency:help
mvn dependency:analyze
mvn dependency:tree
mvn dependency:tree -Dverbose
依赖管理解决什么问题
当同一个工程内有多个模块时,并且要求多个模块使用某个 jar 包的相同版本,为了方便统一版本号,升级版本号,需要提取出一个父亲模块来管理子模块共同依赖的 jar 包版本。
举个�:有两个模块 projectA, projectB,它们的依赖分别如下所示:
projectA:
...
group-a
artifact-a
1.0
group-c
excluded-artifact
group-a
artifact-b
1.0
bar
runtime
projectB:
...
group-c
artifact-b
1.0
war
runtime
group-a
artifact-b
1.0
bar
runtime
projectA 和 projectB 共同依赖了 group-a/artifact-b/1.0,提取公共依赖,生成 parent, parent 依赖如下:
...
group-a
artifact-b
1.0
bar
runtime
则 projectA 和 projectB 均不需要指定 group-a/artifact-b 的 version 信息,未来升级 version 信息时,只需要在 parent 内部指定。
projectA:
...
group-a
artifact-b
1.0
group-a
artifact-a
1.0
group-c
excluded-artifact
group-a
artifact-b
projectB:
...
group-c
artifact-b
1.0
war
runtime
group-a
artifact-b
依赖范围
如果不显示执行 属性时,默认 compile。
scope 有哪些属性:compile, provided, runtime, test, system 等。
详细参考:依赖范围
最佳实践
(1)项目中源代码使用的 jar 包一定在 pom.xml 中显示引用。
(2)经常 check 一下包冲突,检查是否需要处理。
(3)当使用多个模块时,parent 一定要使用包管理模块来规范 Jar 包版本,而不是包依赖模块直接引入依赖。 dependencyManagement vs dependencies
第二个问题:
第一板斧:找到传递依赖的鬼出在哪里?
dependency:tree是把照妖照,pom.xml用它照照,所有传递性依赖都将无处遁形,并且会以层级树方式展现,非常直观。
以下就是执行dependency:tree后的一个输出:
引用
[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时,我用到了“无处遁形”,其实有时你会发现简单地用dependency:tree往往并不能查看到所有的传递依赖。不过如果你真的想要看所有的,必须得加一个-Dverbose参数,这时就必定是最全的了。
全是全了,但显示出来的东西太多,头晕目眩,有没有好法呢?当然有了,加上Dincludes或者Dexcludes说出你喜欢或讨厌,dependency:tree就会帮你过滤出来:
引用
Dincludes=org.springframework:spring-tx
过滤串使用groupId:artifactId:version的方式进行过滤,可以不写全啦,如:
mvn dependency:tree -Dverbose -Dincludes=asm:asm
就会出来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]------------------------------------------------------------------------
对asm有依赖有一个直接的依赖(asm:asm:jar:3.2)还有一个传递进入的依赖(asm:asm:jar:1.5.3)
第二板斧:将不想要的传递依赖剪除掉
承上,假设我们不希望asm:asm:jar:1.5.3出现,根据分析,我们知道它是经由org.unitils:unitils-dbmaintainer:jar:3.3引入的,那么在pom.xml中找到这个依赖,做其它的调整:
org.unitils
unitils-dbmaintainer
${unitils.version}
dbunit
org.dbunit
asm
asm
再分析一下,你可以看到传递依赖没有了:
[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
第三板斧:查看运行期类来源的JAR包
有时,你以为解决了,但是偏偏还是报类包冲突(典型症状是java.lang.ClassNotFoundException或Method不兼容等异常),这时你可以设置一个断点,在断点处通过下面这个我做的工具类来查看Class所来源的JAR包:
packagecom.ridge.util;importjava.io.File;importjava.net.MalformedURLException;importjava.net.URL;importjava.security.CodeSource;importjava.security.ProtectionDomain;/***@author: chenxh
* @date: 13-10-31*/
public classClassLocationUtils {/*** 获取类所有的路径
*@paramcls
*@return
*/
public static String where(finalClass cls) {if (cls == null)throw new IllegalArgumentException("null input: cls");
URL result= null;final String clsAsResource = cls.getName().replace('.', '/').concat(".class");final ProtectionDomain pd =cls.getProtectionDomain();if (pd != null) {final CodeSource cs =pd.getCodeSource();if (cs != null) result =cs.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(clsAsResource));else if (newFile(result.getFile()).isDirectory())
result= newURL(result, clsAsResource);
}catch(MalformedURLException ignore) {}
}
}
}if (result == null) {final ClassLoader clsLoader =cls.getClassLoader();
result= clsLoader != null ?clsLoader.getResource(clsAsResource) :
ClassLoader.getSystemResource(clsAsResource);
}returnresult.toString();
}
}
随便写一个测试,设置好断点,在执行到断点处按alt+F8动态执行代码(intelij idea),假设我们输入:
ClassLocationUtils.where(org.objectweb.asm.ClassVisitor.class)
即可马上查出类对应的JAR了:
这就是org.objectweb.asm.ClassVisitor类在运行期对应的JAR包,如果这个JAR包版本不是你期望你,就说明是你的IDE缓存造成的,这时建议你Reimport一下maven列表就可以了,如下所示(idea):
Reimport一下,IDE会强制根据新的pom.xml设置重新分析并加载依赖类包,以得到和pom.xml设置相同的依赖。(这一步非常重要哦,经常项目组pom.xml是相同的,但是就是有些人可以运行,有些人不能运行,俗称人品问题,其实都是IDE的缓存造成的了
idea清除缓存,为了提高效率不建议采用reimport重新起开启项目的方式,建议采用idea自带的功能,File->Invalidate Caches 功能直接完成清除idea cache
三、另一个问题,log冲突:项目中出现的问题如下:
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.apache.log4j.Log4jLoggerFactory
后经网上搜索加边上大牛指点发现:
log4j-over-slf4j.jar 和 slf4j-log4j12.jar 在同一个classpath下就会出现这个错误。
解决方法:
将slf4j-log4j12.jar从相关的jar中排除
但是查看maven项目中的pom文件,自己并没有配置这个jar的依赖,猜测是maven加载其他jar引入的依赖包。
打开pom.xml文件,在Dependency Hierarchy(依赖列表)中查看jar包的依赖层次关系。
在过滤栏中输入log4j,右侧出现了log4j相关包的依赖结构,左侧则是pom.xml全部依赖包的列表展示。
直接在右侧选中zookeeper底下的slf4j的jar包,右键选择Exclude,然后保存pom.xml。这样在加载zookeeper的jar包时就不会再加载slf4j的jar包。
修改后对应的dependency文件如下:
org.apache.zookeeper
zookeeper
3.4.6
slf4j-log4j12
org.slf4j
这样就能通过filter过滤快速找到对应jar,并知道他的依赖关系,快速解决项目中的jar包冲突问题。