Maven中的依赖管理
目录
什么是依赖范围
什么是依赖传递
依赖范围对依赖传递的影响
依赖冲突
什么是依赖冲突
依赖冲突的解决方案
版本锁定
短路径优先
编辑
声明优先
特殊优先(后来者居上)
可选依赖
排除依赖
可选依赖和排除依赖的区别
刷新依赖的8种方式
资源文件的指定
在Java开发中,项目的依赖管理是一项重要任务。通过合理管理项目的依赖关系,我们可以有效的管理第三方库,模块的引用及版本控制。而Maven作为一个强大的构建工具和依赖管理工具,为我们提供了便捷的方式来管理项目的依赖。
什么是依赖范围
Maven的依赖构件包含一个依赖范围的属性。这个属性描述的是三套classpath的控制,即编译、测试、运行。这说白了就是添加的jar包起作用的范围。 maven提供了以下几种依赖范围:compile,test,provided.runtime,system。
分别介绍如下:
- compile:编译依赖范围,如果没有指定,默认使用该依赖范围,对于编译、测试、运行3种classpath都有效。
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.24</version><scope>compile</scope></dependency> </dependencies>
- test:测试依赖范围,使用此依赖范围的maven依赖,只对编译测试、运行测试的classpath有效,在编译主代码、运行项目时无法使用此类依赖。比如junit,它只有在编译测试代码及运行测试的时候才需要。
<dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency> </dependencies>
- provided:已提供依赖范围。表示项目的运行环境中已经提供了所需要的构件,对于此依赖范围的maven依赖,对于编译源码、编译测试、运行测试中classpath有效,但在运行时无效。比如上面说到的servlet-api,这个在编译和测试的时候需要用到,但是在运行的时候,web容器已经提供了,就不需要maven帮忙引入了。
<dependencies><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope></dependency> </dependencies>
- runtime:运行时依赖范围,使用此依赖范围的maven依赖,对于测试和运行项目的classpath有效,但在编译时无效,比如jdbc驱动实现,项目代码编译的时候只需要提供JDK提供的JDBC接口,运行的时候才需要具体的jdbc驱动实现。
<dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.32</version><scope>runtime</scope></dependency> </dependencies>
- system:系统依赖范围,该依赖与3中classpath的关系,和provided依赖范围完全一致。但是,使用system范围的依赖时必须通过systemPath元素显示第指定依赖文件的路径。这种依赖直接依赖于本地路径中的构件,可能每个开发者机器中构件的路径不一致,所以如果使用这种写法,你的机器中可能没有问题,别人的机器中就会有问题,所以建议谨慎使用。
<dependencies><dependency><groupId>com.bjpowernode</groupId><artifactId>maven_001_javase</artifactId><version>1.0-SNAPSHOT</version><scope>system</scope><systemPath>D:/repository/com/bjpowernode/maven_001_javase/1.0-SNAPSHOT/maven_001_javase-1.0-SNAPSHOT.jar</systemPath></dependency> </dependencies>
总结:
什么是依赖传递
依赖具有传递性。 Maven 的依赖传递机制是指:不管 Maven 项目存在多少间接依赖,POM 中都只需要定义其直接依赖,不必定义任何间接依赖,这在一定程序上简化 了POM 的配置。
假项目A依赖项目B,项目B依赖项目C,则A----->直接依赖B,B----->直接依赖C,A----->间接依赖C。
直接依赖和间接依赖是一个相对的概念。直接在项目中配置的依赖称为直接依赖,通过添加依赖关联进来的依赖称为间接依赖。1是项目的直接依赖,2是1的直接依赖,2是项目的间接依赖,以此类推。
如图:
依赖范围对依赖传递的影响
B 是 A 的直接依赖,C 是 A 的间接依赖,根据 Maven 的依赖传递机制,间接依赖 C 会以传递性依赖的形式引入到 A 中,但这种引入并不是无条件的,它会受到依赖范围的影响。
图示依赖传递关系:
规范化依赖传递的结果:
交叉部分的单元格的取值为传递性依赖的依赖范围,若交叉单元格取值为 “-”,则表示该传递性依赖不能被传递。
通过上表,可以总结出以下规律:
- 当间接依赖的范围是compile时,与直接依赖的范围一致;
- 当间接依赖的范围是test或provided时,传递性依赖不会被传递;
- 当间接依赖的范围是runtime时,传递性依赖的范围与直接依赖的范围一致,此时传递性依赖的范围为runtime。
依赖冲突
什么是依赖冲突
在 Maven 项目中,依赖通常被定义在项目的 pom.xml 文件中。当多个依赖项引入了不同版本的相同库时,就会发生依赖冲突。这可能是因为项目的直接依赖和间接依赖导致了同一库的多个版本存在于类路径中。每个显式声明的类包都会依赖于一些其它的隐式类包,这些隐式的类包会被maven间接引入进来,从而造成类包冲突。
依赖冲突的解决方案
Maven可以通过以下途径解决依赖冲突。
版本锁定
在父工程中使用dependencyManagement 进行版本锁定,dependencyManagement可以统一管理整个项目的版本号,确保应用的各个项目的依赖和版本一致。 dependencyManagement只是声明依赖,并不自动实现引入,因此子项目需要显示的声明需要用的依赖,便可以忽略版本号。如果排斥父工程中定义的版本号,可以显示的进行版本号声明。
- 子工程使用父工程锁定的版本号
- 子工程使用自定义的版本号,只要重新声明即可
- 父工程不使用<dependencyManagement>标签,则子工程跟父工程完全保持一致。子工程不需要显示依赖任何jar包。
短路径优先
引入路径短者优先,顾名思义,当一个间接依赖存在多条引入路径时,引入路径短的会被解析使用。如图 :
声明优先
如果存在短路径,则优先选择短路径,如果路径相同的情况下,先声明者优先,POM 文件中依赖声明的顺序决定了间接依赖会不会被解析使用,顺序靠前的优先使用。如图。
代码示例:
注意去掉<scope>标签,否则会因为依赖范围的影响导致效果无法显示。
特殊优先(后来者居上)
同一个pom.xml文件中进行了多次依赖jar包不同版本的配置,后面的覆盖前面的配置。这种情况比较少见。
可选依赖
maven_03项目可选择是否传递间接依赖junit_4.13,主动权在当前项目maven_03中。如果当前项目被依赖到其它项目中,当前项目可以拒绝交出间接依赖项。例如maven_02添加了maven_03的依赖,maven_03可以自主设置其依赖项junit_4.13是否被间接传递。<optional>true</optional> 为不传递间接依赖,那么在maven_02项目中就没有junit_4.13的依赖。默认是false,是传递间接依赖。
代码示例:
排除依赖
是当前项目是否主动断开其依赖项目的间接依赖。也就是控制当前项目是否使用其直接依赖传递下来的接间依赖。在maven_02项目中添加maven_03项目的依赖,但不要maven_03项目中的junit_4.13的依赖,可以选择排除依赖。这样可以保证当前项目依赖的纯净性。
排除依赖使用 exclusions 元素排除依赖,说明如下:
- exclusions 元素下可以包含若干个 exclusion 子元素,用于排除若干个间接依赖,该元素包含两个子元素:groupId 和 artifactId,用来确定需要排除的间接依赖的坐标信息
- exclusion 元素中只需要设置 groupId 和 artifactId 就可以确定需要排除的依赖,无需指定版本version
如图:
代码示例:
可选依赖和排除依赖的区别
排除依赖和可选依赖都能在项目中将间接依赖排除在外,但两者实现机制却完全不一样。
- 可选依赖是自己决定是否向外提供间接依赖(maven_03设置拒绝提供间接依赖junit)
- 排除依赖是主动拒绝添加直接依赖关联的间接依赖(maven_02项目设置排除maven_03的junit依赖)
- 可选依赖的优先级高于排除依赖
- 若对于同一个间接依赖同时使用排除依赖和可选依赖进行设置,那么可选依赖的取值必须为 false,否则排除依赖无法生效。
刷新依赖的8种方式
在idea中有时候会出现刷新延时的情况,那么需要进行手工刷新依赖。
- 点击M刷新按钮。
- 点Maven窗口的Reload All Maven Projects。
- Build--->ReBuild Project 重新构建项目的同时刷新所有依赖。
- 点击本项目的pom.xml文件--->右键--->Maven--->Reload Project 刷新本项目的依赖。
- 打开pom.xml文件,全选,拷贝,删除,关闭,打开,粘贴.物理刷新pom.xml文件 。
- Invalidate Caches--->全选--->Invalidate and Restart 清空idea的缓存并重启idea刷新依赖。
- 打开本地仓库,搜索last,全选删除,点Maven的刷新全部依赖的按钮。
- 在7的步骤后执行File--->settings--->Build,Execution,Deployment--->Build Tools--->Maven--->Repositories--->选中本地仓库--->update--->ok。
资源文件的指定
src/main/java 和 src/test/java 这两个目录中的所有*.java 文件会分别在 comile 和 test-comiple 阶段被编译,编译结果分别放到了 target/classes 和 targe/test-classes 目录中,但是这两个目录中的其他文件(后缀是.properties或.xml等文件)都会被忽略掉(编译后丢失),如果需要把 src 目录下的除.java之外的文件包放到 target/classes 目录,作为输出的 jar 一部分。需要指定资源文件位置。以下内容放到<build>标签中。简单来说就是在resources目录下的*.properties文件和*.xml文件编译时不丢失,但resources目录外的*.properties文件和*.xml文件会丢失,所以要指定位置,保证编译后文件都在.
代码示例:
添加指定后:
指定代码:
<build><resources><resource><!--指定java目录下的所有路径下的所有文件--><directory>src/main/java</directory><includes><include>**/*.xml</include><include>**/*.properties</include></includes></resource><resource><!--指定resources目录下的所有路径下的所有文件--><directory>src/main/resources</directory><includes><include>**/*.xml</include><include>**/*.properties</include></includes></resource></resources>
</build>