如何解决包冲突问题 博客分类: Java相关 包冲突
随着业务需求的不断扩展,应用中代码量也会逐渐增长,工程中引用的二方包或者三方包也自然而然会越来越多。因此,不可避免,可能存在引用的二方包或三方包相互冲突所导致的系统问题。
本文将针对前段时间遇到的实际案例进行分析,旨在当遇到包冲突问题时该如何解决,并提供同事用 python 写的一个发现包冲突的小工具(十分有用!)
一 发现问题:
首先 ,让我们看下异常,这是在应用启动后,执行具体操作时所报的错误:
Caused by: java.lang.NoSuchMethodError: com.google.common.collect.MapMaker.expireAfterWrite(JLjava/util/concurrent/TimeUnit;)Lcom/google/common/collect/MapMaker;
at com.taobao.treasure.client.TreasureClientImpl.<init>(TreasureClientImpl.java:31)
at com.taobao.treasure.client.TreasureClientFactory$1.getPipeline(TreasureClientFactory.java:95)
at org.jboss.netty.bootstrap.ClientBootstrap.co
nnect(ClientBootstrap.java:212)
at org.jboss.netty.bootstrap.ClientBootstrap.connect(ClientBootstrap.java:188)
at com.taobao.treasure.client.TreasureClientFactory$2.call(TreasureClientFactory.java:261)
at com.taobao.treasure.client.TreasureClientFactory$2.call(TreasureClientFactory.java:248)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at com.taobao.treasure.client.TreasureClientFactory.getClient(TreasureClientFactory.java:239)
... 53 more
根据错误我们大概可以了解,工程中引用了一个名叫 com.google.common.collect.MapMaker 的类,并调用了其中名为 expireAfterWrite() 的方法,但是系统在加载该方法时却表示没有找到该方法。
这是一个很明显的包冲突问题,这类情况的发生很可能是因为如下状况引起的:
如图,你引用了 2 个三方包 a.jar 和 b.jar , a.jar 中又引用了一个 c.jar ,假设 c.jar 的版本号为 version-1, b.jar 中也引用了 c.jar ,假设这里的 c.jar 相对于 a.jar 中的 c.jar 为较高版本,记为 version-2 , b.jar 中某个类引用了 c.jar 的类 classA 中的方法 method A() ,并且该方法只存在于高版本的 c.jar(version-2) 的类 classA 中,而不存在 c.jar(version-1) 的类 classA 中。
当系统编译加载时,系统可能编译加载 c.jar(version-1) ,也可能编译加载 c.jar(version-2) ,当编译加载 c.jar(version-2) 时,由于很多 jar 包都支持向下兼容,即高版本兼容低版本,因此不论 a.jar 调用 c.jar 还是 b.jar 调用 c.jar 一般都不会出问题。但如果此时刚好应用编译加载的是 c.jar(version-1) 中的类 classA 时,那么 b.jar 调用 Method A() 时便会报上述错误,因为 Method A() 函数只存在于高版本的 c.jar 中,而此时系统编译加载的却是低版本的 c.jar 。
二 解决问题
当遇到这类问题我们该如何解决呢 ? 主要有以下三步:
第一, 发现是哪个类发生了冲突;
第二, 发现冲突 jar 包,即冲突类存在于哪个 Jar 包中;
第三, 发现这个冲突 Jar 包是自身系统直接引用的还是系统引用的 Jar 间接引用的。
针对上述第一步,我们使一个 用 python 写的 名为 conflictdetect 小工具来解决。
将 conflictdetect.exe 下载后,存放到某个目录,然后将 conflictdetect.exe 的存放路径设定至环境变量 path 中,打开 CMD ,到 jar 包所在目录。
运行:
conflictdetect.exe
就会检测到目录中存在冲突的 jar 包,并以三种格式输出 ,: 只输出 jar ,只输出 class, 两个都输出。
通过 conflictdetect –h 可以查看用法。
本人将 conflictdect.exe 放置于 D:\exe.win32-3.2 下,因此在环境变量 Path 中设置的是 D:\exe.win32-3.2 ,打开 CMD ,定位到应用存放 jar 包的目录下,执行 :
conflictdetect.exe –t –f –o “D:/out.log”
此时,在 D 盘中将会发现一个名为 out.log 的文档,里面记录了存放 jar 包目录下所有冲突的 class 和 jar 。在其中我们 search 一把上述系统冲突的类名“ MapMaker ”,发现他们原来是存在于 google-collections-1.0.jar' 和 'guava-r09.jar' 中。 如下图:
org/springframework/remoting/jaxrpc/JaxRpcPortProxyFactoryBean.class=['spring-2.0.7.jar', 'spring-remoting-1.2.7.jar']
com/alibaba/service/uribroker/DefaultURIBrokerService.class=['toolkit-service-uribroker-1.0.jar', 'toolkit-webx-all-in-one-2.0.jar']
com/alibaba/service/pool/RecyclableSupport.class=['toolkit-service-pool-1.0.jar', 'toolkit-webx-all-in-one-2.0.jar']
com/google/common/collect/MapMaker$1.class=['google-collections-1.0.jar', 'guava-r09.jar']
org/springframework/core/task/SimpleAsyncTaskExecutor.class=['spring-2.0.7.jar', 'spring-core-2.0.6.jar']
此时,对系统有影响的冲突类和冲突 jar 包我们都已经发现了。
通过网上百度,原来 google-collections-1.0.jar 和 guava.jar 都 google 的产品,并且 guava.jar 是 google-collections-1.0.jar 升级版本,因此在编译过程中我们应该把 guava.jar 编译进工程,而不能把 google-collections-1.0.jar 编译进工程。
第三步,我们看 google-collections-1.0.jar 是否是应用直接引用的。经过确认,我的项目中并没有直接引用这两个 jar 包,因此可能是通过其他 jar 包间接引用进来的。由于项目中是通过 maven 进行引用 jar 包的管理。因此, 结合 maven 的命令 mvn dependency:tree 可以很容易发现这两个jar 包到底是通过哪些jar 包间接引用进来的。
+- com.know.diamond:diamond-sdk:jar:2.0.5:compile
[INFO] | +- net.sourceforge.htmlunit:htmlunit:jar:1.14:compile
[INFO] | | +- rhino:js:jar:1.6R7:compile
[INFO] | | +- nekohtml:nekohtml:jar:0.9.5:compile
[INFO] | | \- net.sourceforge.cssparser:cssparser:jar:0.9.4:compile
[INFO] | +- com.taobao.diamond:diamond-utils:jar:2.0.5:compile
[INFO] | | +- org.codehaus.jackson:jackson-core-lgpl:jar:1.4.0:compile
[INFO] | | \- org.codehaus.jackson:jackson-mapper-lgpl:jar:1.4.0:compile
[INFO] | \- com.google.collections:google-collections:jar:1.0:compile
原来是 diamond-sdk:jar 间接引用了 com.google.collections:google-collections:jar. 因此我们通知 diamond-sdk:jar 的维护者进行 jar 包升级,或者在 MVN 编译过程中强制禁止 google-collections:jar 编译进工程。如下:
<dependency>
<groupId>com.know.diamond</groupId>
<artifactId>diamond-sdk</artifactId>
<version>2.0.5</version>
<exclusions>
<exclusion>
<groupId>com.google.collections</groupId>
<artifactId>google-collections</artifactId>
</exclusion>
</exclusions>
</dependency>
OK,重新编译工程,启动,问题解决。