Maven 篇:正确对待依赖冲突

引言

 当我们使用 Maven 来构建我们的程序时,我们可以用几句配置来代替大量的 Jar 包(一个依赖会引入其依赖的其他依赖,而那些依赖也会引入其依赖的依赖,所以有依赖树这种说法),同时因为这种配置在我们交流代码时可以不用自己引入 Jar 包(避免了版本不一致而出错),只要更新 Maven,它就会在后台帮我们解决这一切。但是在我们享受这种方便的同时,我们也在为这种方便付出代价。

首先我们先来看一个例子

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.1.3.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>${hibernate.version}</version>
</dependency>

其依赖树如下
在这里插入图片描述

 我们可以清楚地看到 hibernate-core 所依赖的 jboss-logging 被省略了 (灰色的那一个依赖),因为 Maven ( 或者说是 Java 的 ClassLoader ) 先加载了 hibernate-validator 所依赖的 jboss-logging,就会忽略其他同名依赖。 一般情况来说, 这是不会出现问题的, 因为在你使用 Maven 时, 你已经不知不觉地通过这种方式多次使你的代码正确执行了, 但是一旦出现了错误, 你多半会被坑的死去活来 ~. ~
比如我举的这个例子, 当你初始化 SessionFactory 时, 你将会见到如下错误提醒:

java.lang.NoSuchMethodError: org.hibernate.internal.CoreMessageLogger.debugf(Ljava/lang/String;II)V

首先检查类或方法是否存在,如果找到类,却没有在该类及其父类中找到该方法,那么百分百就是版本错误。

  • 先找 CoreMessageLogger 类
public interface CoreMessageLogger extends BasicLogger {
}
  • 在 CoreMessageLogger 类中找不到 debugf 方法,在它的父类中查找
package org.jboss.logging;
import org.jboss.logging.Logger.Level;
public interface BasicLogger{
	void debugf(String var1, Object... var2);
	void debugf(String var1, Object var2);
	void debugf(String var1, Object var2, Object var3);
	void debugf(String var1, Object var2, Object var3, Object var4);
	void debugf(Throwable var1, String var2, Object... var3);
	void debugf(Throwable var1, String var2, Object var3);
	void debugf(Throwable var1, String var2, Object var3, Object var4);
	void debugf(Throwable var1, String var2, Object var3, Object var4, Object var5);
}

我们发现了 BasicLogger 是 jboss-logging 中的类,接着我们在来找 debugf(Ljava/lang/String;II)V 这个方法(这种写法是 JNI 字段描述符,翻译之后为 void debugf( String[ ] str , int i, int j ))。
恩。很遗憾,在这个方法中并没有找到需要的方法,那么答案就出来了-- jboss-logging 版本错误
让我们回到最开始,我们知道 Maven 加载的 jboss-logging 版本为 3.1.3,而 hibernate-core 依赖的版本为 3.3.0 ,在该版本的 BasicLogger 中我们可以找到如下方法

void debugf(Stringvar1, int var2);
void debugf(String var1, int var2, int var3);	//	我们需要的那个方法
void debugf(String var1, int var2, Object var3);

P.S. 说句题外话,之所以列了这个例子出来,就是希望正在寻找问题的你了解 Maven 依赖树的重要性,以后遇到这种问题可以知道怎么解决


解决上述问题有几种:

一,将重要的依赖放到前面

 因为 maven 会优先加载第一个遇到的版本,所以我们只需要将 hibernate-core 移到 hibernate-validator 的前面,这样 Maven 就会加载到 3.3.0 这个版本。

<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-core</artifactId>
   <version>5.0.5.Final</version>
</dependency>
<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-validator</artifactId>
   <version>5.1.3.Final</version>
</dependency>

在这里插入图片描述
如上可以看到版本 3.1.3 被忽略

二,使用 exclusions 标记排除依赖

将干扰的依赖排除后就没有后顾之忧了,如下:

<dependency>
    <groupId>org.hibernate</groupId>
       <artifactId>hibernate-validator</artifactId>
    <version>5.1.3.Final</version>
    <exclusions>
        <exclusion>
            <groupId>org.jboss.logging</groupId>
            <artifactId>jboss-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
三,使用 mvn dependency:tree 命令查看依赖树

 推荐最好学会这种方法,虽然使用 IDEA 的 maven 视图可以很容易的看到依赖树的分布,但是因为存在缓存问题,maven 窗口不能实时改变,而且 maven 窗口也没有提供筛选功能,在很多时候不太方便。

mvn dependency:tree 选项
  • verbose 显示全部,不使用此选项则显示部分。当然了,这不是让你一次性显示所有依赖出来,因为太多了,显示出来的话阅读难度指数上升,有兴趣可亲身一试 ~. ~
  • includes 筛选出想要的的依赖,使用时只显示被加载的 jar 包,如果和 verbose 一起使用则可以查看被忽略的 jar 包,如下
  • excludes 这个就没什么好说的,includes 相反的作用

未使用 verbose 的 includes

--- maven-dependency-plugin:2.8:tree (default-cli) @ test ---
 tree:test:war:1.0-SNAPSHOT
 \- org.hibernate:hibernate-validator:jar:5.1.3.Final:compile
 	\- org.jboss.logging:jboss-logging:jar:3.1.3.GA:compile
--------------------------------------------------------------------

使用了 verbose 的includes

--- maven-dependency-plugin:2.8:tree (default-cli) @ test ---
tree:test:war:1.0-SNAPSHOT
+- org.hibernate:hibernate-validator:jar:5.1.3.Final:compile
|  \- org.jboss.logging:jboss-logging:jar:3.1.3.GA:compile
 \- org.hibernate:hibernate-core:jar:5.0.5.Final:compile
	+- (org.jboss.logging:jboss-logging:jar:3.3.0.Final:compile - omitted for conflict with 3.1.3.GA)
	\- org.hibernate.common:hibernate-commons-annotations:jar:5.0.1.Final:compile
		\- (org.jboss.logging:jboss-logging:jar:3.3.0.Final:compile - omitted for conflict with 3.1.3.GA)
 --------------------------------------------------------------------

可以看到被忽略的版本被打上括号并且在最后跟着 omitted for conflict with x.x.x

命令组合

mvn dependency:tree -Dverbose -Dincludes=groupId:artifactId ,如

mvn dependency:tree -Dverbose -Dincludes=org.jboss.logging:jboss-logging

终上所述,当我们遇到 ClassNotFound 或 NoSuchMethod 异常时

  • 我们首先要先去确认这些方法是否存在
  • 接着再去寻找它的父类与接口,一般很快就会找到
    (这是真的,代码的继承深度一般不可能太深)
  • 当我们发现是版本问题时首先就应该定位版本,然后切换成正确版本,方法如下:
    1.通过 IDEA 的 maven 窗口,在灰色选项(被忽略)中查找
    2.通过 mvn dependency:tree -Dverbose -Dincludes=xx:xx
  • 最后通过移动依赖位置覆盖错误依赖或使用 exclusions 标记将其移除

P.S. 本人使用的 IDE 为 IntelliJ IDEA 社区版(免费的),其含有 Maven 窗口和强大的反编译功能,所以才能像我刚才说的那样追踪源码和查看错误。当然了,Eclipse 也是有相应的插件的(习惯用 Eclipse 的同学推荐安装一款反编译的插件,详情见 Java 篇:让你的 Eclipse 飞起来


  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值