一招解决所有依赖冲突
背景介绍
最近遇到了这样一个问题,我们有一个 jar 包 common-tool,作为基础工具包,被各个项目在引用。突然某一天发现日志很多报错。
一看是 NoSuchMethodError,意思是 DisJunction 里 init 方法没找到,但是我检查了代码是有这个方法的啊。
问题定位
当时百度了一下,都说这种情况一般是 jar 包冲突了,但是本地看来下没有 jar 包版本都一致,没有冲突啊,百思不得其解。于是写了个代码看看这个报找不到方法的类到底有没有这个方法。
Constructor<?>[] constructors = Disjunction.class.getConstructors();
for (Constructor constructor:constructors){
//查看类的构造器方法
log.error("find monitor bug:{}",constructor);
}
Class targetclass = Disjunction.class;//可以用自己想知道的类替换
String className = targetclass.getName();
className = className.replace('.', '/');
String resource = "/" + className + ".class";
URL url = targetclass.getResource(resource);
//查看类的全路径
log.error("find monitor bug:{}",url.getFile());
打印出来后发现类全路径竟然不是我的包里的,是一个 agent.jar 里面的。
问了下才知道原来是运维添加了一个 agent 到线上环境,里面的 byte-buddy 依赖和我的冲突了。
如果是本地冲突的话,也可以使用 IDEA 里的 Maven Helper 插件,它可以清晰的指出某个包被哪几个包依赖。
解决方案:
在看解决方案之前先来回顾一下Maven的依赖原则:
- 最短路径优先
即如果我 A->B->C->X1.0,D->E->X2.0,我项目里引入了 A 和 D,那么我的 X 版本是用的 2.0,因为 X2.0 路径最短。
- 申明顺序优先
如果 A-B-X(1.0) ,A-C-X(2.0) 这样的路径长度一样怎么办呢?这样的情况下,maven 会根据 pom 文件声明的顺序加载,如果先声明了 B,后声明了 C,那就最后的依赖就会是 X(1.0)
解决方案 1:
maven 依赖的顺序是路径优先,所以我们可以在项目的 pom 文件里直接申明版本,这样就比项目里的引入的 jar 包里再引入的依赖优先级要高些。
这种方案的缺点就是因为我这个基础 jar 包好几个项目再用,需要每个项目都要修改。
解决方案 2:
使用 maven 插件 maven-shade-plugin
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<includes>
//替换的范围,只替换下面两个包 <include>net.bytebuddy:byte-buddy</include>
<include>net.bytebuddy:byte-buddy-agent</include>
</includes>
</artifactSet>
<createSourcesJar>true</createSourcesJar>
<relocations>
<relocation>
// 将net.bytebuddy依赖重命名为cn.mmc.net.bytebuddy
<pattern>net.bytebuddy</pattern>
<shadedPattern>cn.mmc.net.bytebuddy</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
这种做法就是将原有依赖的 jar 包都重命名一下,比如里面的类是 net.bytebuddy.DisJunction,用这个插件后就变为了 cn.mmc.net.bytebuddy.DisJunction,这样类的全路径不一样,就彻底杜绝了依赖冲突的情况。
另外上面的 includes 是指仅指定替换某些依赖里包名是 net.bytebuddy,否则所有包含了 net.bytebuddy 的都会被重命名,这样打出来的 jar 包就会非常大。
总结
为了一劳永逸,我使用了第二种方案 maven-shade-plugin 的方式,发布之后依赖冲突的问题就解决了。