《Maven依赖管理全解析:范围控制、冲突解决与最佳实践》

一、Maven的依赖管理

在JAVA开发中,项目的依赖管理是一项重要任务。通过合理管理项目的依赖关系,我们可以有效的管理第三方库,模块的引用及版本控制。而Maven作为一个强大的构建工具和依赖管理工具,为我们提供了便捷的方式来管理项目的依赖。

1.什么是依赖范围

Maven的依赖构件包含一个依赖范围的属性。这个属性描述的是三套classpath的控制,即编译、测试、运行。这说白了就是添加的jar包起作用的范围。 maven提供了以下几种依赖范围:compile,test,provided.runtime,system。

分别介绍如下:

⑴.compile

编译依赖范围,如果没有指定,默认使用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帮忙引入了。

⑷.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>
范围作用阶段示例场景代码示例
compile默认值,编译、测试、运行均有效Spring核心库<scope>compile</scope>(可省略)
test仅测试阶段有效JUnit测试框架<scope>test</scope>
provided编译和测试有效,运行由环境提供Servlet API(Tomcat已提供)<scope>provided</scope>
runtime测试和运行有效,编译无需具体实现JDBC驱动(编译时用接口,运行时用实现)<scope>runtime</scope>
system同provided,需手动指定本地路径本地特殊依赖(谨慎使用)<scope>system</scope> + <systemPath>D:/path.jar</systemPath>

2.什么是依赖传递

依赖具有传递性。 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       间接依赖:compile

②直接依赖:compile       间接依赖:test

③直接依赖:compile       间接依赖:provided

④直接依赖:compile       间接依赖:runtime

⑤直接依赖:test      间接依赖:compile
⑥直接依赖:test      间接依赖:test
⑦直接依赖:test      间接依赖:provided
⑧直接依赖:test      间接依赖:runtime
⑨直接依赖:provided    间接依赖:compile
⑩直接依赖:provided    间接依赖:test
⑪直接依赖:provided    间接依赖:provided⑫直接依赖:provided    间接依赖:runtime

⑯剩下的不再做演示

总结

3.依赖冲突

⑴ 什么是依赖冲突

在 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 元素排除依赖,说明如下:

  1. exclusions 元素下可以包含若干个 exclusion 子元素,用于排除若干个间接依赖,该元素包含两个子元素:groupId 和 artifactId,用来确定需要排除的间接依赖的坐标信息
  2. exclusion 元素中只需要设置 groupId 和 artifactId 就可以确定需要排除的依赖,无需指定版本version

代码示例:

4. 刷新依赖的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。

5.资源文件的指定

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>

一般情况下,会两个目录都指定.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值