场景2-同包同名类与环境兼容性
文章目录 Maven依赖传递 Maven冲突处理工具Maven依赖树命令Maven依赖冲突解决 总结
Maven依赖冲突场景 场景1-重构同包同名类
假设有两个业务B和C依赖基础包A,后维护基础包A的团队重构基础包A,并打了一个新的基础包D发布。
在一次业务改动时,业务B引入新的基础包D,在开发测试环境运行没什么问题,但在生产运行时,却报异常 。
具体原因在于 B业务依赖基础包A和D,而A和D存在两个同包同名类,运行的时候,具体加载谁,不同环境还真不一样。
场景2-同包同名类与环境兼容性
业务A使用RPC,而RPC依赖,即 A -> RPC -> -3.18.1.GA
某次修改,业务A又引入另外一个第三方开源包,其依赖-3.15.0-GA,而开发测试环境为JDK7,将-3.15.0-GA打入应用包中没问题,可以运行;但到生产环境时,由于生产环境为JDK8,和-3.15.0-GA不兼容,导致生产环境运行失败。
场景3-同包同名类内部逻辑不同
不同版本同包同名类内部逻辑不同,从而导致业务异常。这种相对较难排查,因为不报错。
综上
归根到底,所有的场景的根本原因是 真正依赖的类并不是自己想要的
Maven依赖传递
Maven依赖具有传递性:
项目 B 中依赖了 和 Guava 两个jar包。然后项目 A 又依赖了项目 B,所以项目 A 也会依赖 和 Guava 两个jar包。
Maven依赖传递包选择逻辑-仲裁机制
三个原则:
优先按照依赖管理元素中指定的版本声明进行仲裁时,下面的两个原则都无效了
不同距离,距离近优先——短路径优先;
项目依赖了项目 A 和项目 B,A 和 B 分别依赖了 Guava,但是从依赖层次来看,项目 B 的层次更浅,故 .0 会被优先选择
相同距离,前者优先——若路径相同,则看 pom 中声明的顺序
项目 A 和项目 B 都分别依赖了 .0 和 .0 的版本,但是项目 A 的顺序在项目 B 的前面,所以会优先选择 .0 版本。
Maven遵循以上三个原则进行jar包选择,所以可能导致jar包的错误选择,毕竟在jar引入时,涉及到多个二方包及三方包引入,jar依赖关系错综复杂,没有精力也不可能要求开发人员去遵守以上两个原则来保证Maven能正确的选择jar包。
Maven scope属性
Maven 项目可以分为三个阶段:编译(),测试(test),运行()。
通过 scope 属性,可以决定依赖应用是否参与以上阶段,也将会影响依赖传递。
Maven提供6种 scope :、、、test、、。
scope有效范围(、test、)示例
、test、
Maven scope默认属性
、test
API,运行项目时,类似容器已经提供
test、
JDBC驱动,只使用JDBC标准接口,不会与具体数据库绑定。
后续如果想要谢欢数据库,只需要修改Maven驱动包即可
test
test
junit
、test
使用范围的依赖时必须通过元素显式地指定依赖文件的路径。
由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植,因此应该谨慎使用。元素还可以引用环境变量
只能在下使用,且type需要为 pom
-boot依赖,解决单继承问题,也可以更好将依赖分类
Maven scope属性还会影响依赖传递,依赖顺序:A -> B -> C
test
test
test
第一列是B的scope属性,第一行是C的scope属性
从上表可以看到:
当C的scope属性是和test时,依赖是不传递的;当且仅当B的scope为,为,A 将会间接依赖 C,且scope为。其他情况下,A的scope将会与 B 的scope一致。 Maven冲突处理工具
idea中的maven 插件非常好用
在 中选择中定位到项目中冲突的jar包,或者在依赖冲突时根据错误日志,定位到冲突类,定位相应 jar 包,手动通过排除。
Maven依赖树命令
不用借助于开发工具的插件,可以直接用 Maven 命令来查看当前项目的依赖关系,命令行进入到要分析的项目目录下,执行下面的命令将分析结果保存到文件中:
mvn dependency:tree > tree.log
Maven依赖冲突解决 排除冲突依赖
使用过Maven 配置,如
<dependency><groupId>com.github.yinjihuangroupId><artifactId>smjdbctemplateartifactId><version>1.1version><exclusions><exclusion><groupId>com.google.guavagroupId><artifactId>guavaartifactId>exclusion>exclusions>
dependency>
父pom中统一管理版本
对于多模块的项目,每个子模块都去依赖不同的版本,这样很容易出问题,一般建议在父 pom 中 来统一管理版本,子模块直接统一使用父 pom 中定义好的版本,这样使得同一项目内的所有模块对于某个相同依赖的版本都是一致的。
可以使用以下命令统一修改父pom的版本号,并将子pom中引用的父版本号一并修改
mvn versions:set -DnewVersion=XXX
Maven可选依赖
Maven的选项可以有选择的传递依赖,如
父模块A的pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0modelVersion><groupId>AgroupId><artifactId>AartifactId><version>1.0-SNAPSHOTversion><dependencies><dependency><groupId>joda-timegroupId><artifactId>joda-timeartifactId><version>2.9.9version><optional>trueoptional>dependency>dependencies>
project>
子模块B的pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0modelVersion><groupId>BgroupId><artifactId>BartifactId><version>1.0-SNAPSHOTversion><dependencies><dependency><groupId>AgroupId><artifactId>AartifactId><version>1.0-SNAPSHOTversion>dependency>dependencies>
project>
可以看到父模块A的依赖使用了true配置,这种情况下,子模块B就不能间接依赖父模块A中的依赖了。
使用场景:
要封装一个通用的模块 ,这个模块中有很多通用的功能,项目 A 依赖只需要使用功能 A,项目 B 依赖只需要使用功能 B。
每个功能都依赖了三方的 Jar,这个时候如果不做任何处理,只要依赖了这个通用的模块 ,那么也就会间接依赖这两个功能的第三方 Jar。
这个时候可以通过设置 =true 来解决这个问题,依赖了通用模块 ,如果要使用 A 功能,那么必须显示依赖 A 功能需要的三方依赖才可以。
总结
应用项目中使用统一管理基础依赖,定义统一的版本,如常用中间包,工具包,日志包。
二方包中不要引入无关的依赖,做到尽量少的依赖。团队开发中,比较常见情况是二方包继承公共的父 pom,从而导致继承许多无相关的依赖,这种情况可以单独管理。
二方包做好向下兼容,不要随意改动现有类名,方法名,字段名。
项目应用上线之前,将替换成正式版本。虽然修改起来很方便,但是正因为这个特性,可以被随便修改。如果某次生产打包发布不注意,就会引入。
二方包不要使用同一个包名,类名。一般来说,团队开发中,包名,类名一样概率比较小。这种比较容易出现在一些重构项目,复制原来类,重构打包发布。对于情况下可以修改包名。
如 -lang3 是 -lang 升级版, -lang3包名为 mons.lang3,而 -lang 包名为 mons.lang。
参考:
好机会,我要帮女同事解决Maven冲突问题
程序员需要了解依赖冲突的原因以及解决办法
maven scope 的作用