欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

如何解决包冲突问题 博客分类: Java相关 包冲突

程序员文章站 2024-03-16 07:59:51
...

随着业务需求的不断扩展,应用中代码量也会逐渐增长,工程中引用的二方包或者三方包也自然而然会越来越多。因此,不可避免,可能存在引用的二方包或三方包相互冲突所导致的系统问题。

 

本文将针对前段时间遇到的实际案例进行分析,旨在当遇到包冲突问题时该如何解决,并提供同事用 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() 的方法,但是系统在加载该方法时却表示没有找到该方法。

 

这是一个很明显的包冲突问题,这类情况的发生很可能是因为如下状况引起的:

 

如何解决包冲突问题
            
    
    博客分类: Java相关 包冲突

 

 

如图,你引用了 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,重新编译工程,启动,问题解决。

 

相关标签: 包冲突