Maven中 jar包冲突原理与解决办法

Maven中jar包冲突是开发过程当中比较常见而又使人头疼的问题,咱们须要知道 jar包冲突的原理,才能更好的去解决jar包冲突的问题。本文将从jar包冲突的原理和解决两个方面阐述Maven中jar包冲突的解决办法。java

1、Maven中jar包冲突产生缘由

MAVEN项目运行中若是报以下错误:web

Caused by:java.lang.NoSuchMethodError
Caused by: java.lang.ClassNotFoundException

十有八九是Maven jar包冲突形成的。那么jar包冲突是如何产生的?spring

首先咱们须要了解jar包依赖的传递性。svg

一、依赖传递

当咱们须要A的依赖的时候,就会在pom.xml中引入A的jar包;而引入的A的jar包中可能又依赖B的jar包,这样Maven在解析pom.xml的时候,会依次将A、B 的jar包所有都引入进来。google

举个例子:
在Spring Boot应用中导入Hystrix和原生Guava的jar包:.net

<!--原生Guava API-->
<dependency>
	<groupId>com.google.guava</groupId>
	<artifactId>guava</artifactId>
	<version>20.0</version>
</dependency>

<!--hystrix依赖(包含对Guava的依赖)-->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
	<version>1.4.4.RELEASE</version>
</dependency>

利用Maven Helper插件获得项目导入的jar包依赖树:
这里写图片描述
从图中能够看出Hystrix包含对Guava jar包依赖的引用: Hystrix -> Guava,因此在引入Hystrix的依赖的时候,会将Guava的依赖也引入进来。插件

二、jar包冲突原理

那么jar包是如何产生冲突的?
假设有以下依赖关系:3d

A->B->C->D1(log 15.0):A中包含对B的依赖,B中包含对C的依赖,C中包含对D1的依赖,假设是D1是日志jar包,version为15.0日志

E->F->D2(log 16.0):E中包含对F的依赖,F包含对D2的依赖,假设是D2是同一个日志jar包,version为16.0code

当pom.xml文件中引入A、E两个依赖后,根据Maven传递依赖的原则,D一、D2都会被引入,而D一、D2是同一个依赖D的不一样版本。
当咱们在调用D2中的method1()方法,而D1中是15.0版本(method1多是D升级后增长的方法),可能没有这个方法,这样JVM在加载A中D1依赖的时候,找不到method1方法,就会报NoSuchMethodError的错误,此时就产生了jar包冲突。

注:
若是在调用method2()方法的时候,D一、D2都含有这个方法(且升级的版本D2没有改动这个方法,这样即便D有多个版本,也不会产生版本冲突的问题。)

举个例子:
这里写图片描述
利用Maven Helper插件分析得出:Guava这个依赖包产生冲突。
咱们以前导入了Guava的原生jar包,版本号是20.0;而如今提示Guava产生冲突,且冲突发生位置是Hystrix所在的jar包,因此能够猜想Hystrix中包含了对Guava不一样版本的jar包的引用。

为了验证咱们的猜测,使用Maven Helper插件打印出Hystrix依赖的jar tree:
这里写图片描述
能够看到:Hystrix jar中所依赖的Guava jar包是15.0版本的,而咱们以前在pom.xml中引入的原生Guava jar包是20.0版本的,这样Guava就有15.0 与20.0这两个版本,所以发生了jar包冲突。

2、 Maven中jar包冲突的解决方案

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,那么就选择最早声明。

二、移除依赖:用于排除某项依赖的依赖jar包

(1)咱们能够借助Maven Helper插件中的Dependency Analyzer分析冲突的jar包,而后在对应标红版本的jar包上面点击execlude,就能够将该jar包排除出去。
这里写图片描述
再刷新之后冲突就会消失。

(2)手动排除
或者手动在pom.xml中使用<exclusion>标签去排除冲突的jar包(上面利用插件Maven Helper中的execlude方法其实等同于该方法):

<dependency>
	<groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
		<version>1.4.4.RELEASE</version>
		<exclusions>
			<exclusion>
				<groupId>com.google.guava</groupId>
				<artifactId>guava</artifactId>
			</exclusion>
	</exclusions>
</dependency>

mvn分析包冲突命令:

mvn dependency:tree

3 版本锁定原则:通常用在继承项目的父项目中

正常项目都是多模块的项目,如moduleA和moduleB共同依赖X这个依赖的话,那么能够将X抽取出来,同时设置其版本号,这样X依赖在升级的时候,不须要分别对moduleA和moduleB模块中的依赖X进行升级,避免太多地方(moduleC、moduleD…)引用X依赖的时候忘记升级形成jar包冲突,这也是实际项目开发中比较常见的方法。

首先定义一个父pom.xml,将公共依赖放在该pom.xml中进行声明:

<properties>
    <spring.version>spring4.2.4</spring.version>
<properties>

<dependencyManagement>
    <dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>${spring.versio}</version>
		</dependency>
	</dependencies>
</dependencyManagement>

这样如moduleA和moduleB在引用Spring-beans jar包的时候,直接使用父pom.xml中定义的公共依赖就能够:
moduleA在其pom.xml使用spring-bean的jar包(不用再定义版本):

<dependencies>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-beans</artifactId>
	</dependency>
</dependencies>

moduleB在其pom.xml使用spring-bean的jar包如上相似:

<dependencies>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-beans</artifactId>
	</dependency>
</dependencies>

以上就是平常开发中解决Maven冲突的几个小方案,固然实际开发中jar包冲突的问题可能远远比这个更复杂,须要具体问题具体处理。


2018/07/20 15:14:00 in SH.

参考:https://blog.csdn.net/u011955252/article/details/78927427