重学Maven工具(二) : Maven依赖的配置、范围、传递、冲突、排除、版本锁定、继承和聚合


一、依赖的管理

依赖指的是项目与jar包之间的相互依赖,比如我们在使用Spring框架的时候,需要用到Spring依赖的jar包。依赖管理指的就是使用Maven来管理项目中使用到的jar包,Maven管理的方式就是“自动下载项目所需要的jar包,统一管理jar包之间的依赖关系 比如在文件上传的时候,光光有common-fileupload这个jar包是不行的,common-fileupload还依赖于common-io这个包jar包,此时由于Maven的依赖管理机制,都会帮我们自动下载这些包,我们并不需要管。

在这里插入图片描述

我们一般添加依赖都会去Maven的仓库去查找,网站为:https://mvnrepository.com/

二、依赖的配置

依赖的配置就是我们在Maven的项目中的pom.xml中来配置我们的依赖。这一步大家都知道,但是对于Maven坐标中的其它属性可能并不认识,下面介绍一下:

<dependencies>
    <dependency>
        <groupId>junit</groupId>     
        <artifactId>junit</artifactId>     
        <version>4.11</version>
        <package>...</package>
        <type>...</type>
        <scope>...</scope>
        <optional>...</optional>
        <exclusions>     
            <exclusion>     
              <groupId>...</groupId>     
              <artifactId>...</artifactId>     
            </exclusion>
      </exclusions>     
    </dependency>        
</dependencies>

这些元素标签的详细介绍:

  • dependencies:用来管理依赖的总标签,一个 pom.xml 文件中只能存在一个这样的标签。
  • dependency:包含在dependencies标签中,可以有无数个,每一个表示一个依赖。
  • groupId , artifactId 和 version:依赖的基本坐标,简称gav, 对于任何一个依赖来说,基本坐标是最重要的,Maven根据坐标才能找到需要的依赖。
  • package:依赖的打包类型。普通项目为jar包,Web项目为war包。
    type:依赖的类型,对应于项目坐标定义的packaging。大部分情况下,该元素不必声明,其默认值是jar。
  • scope:依赖的范围,默认值是 compile。后面会进行详解。
  • optional:标记依赖是否可选。
  • exclusions:用来排除传递性依赖,后面会进行详细介绍。

三、依赖的范围

依赖的范围:就是该依赖在各种环境下是否还可以使用。比如测试的jar包 junit4.11,我们只希望在代码测试的时候使用,不希望在布署项目的时候把这个jar包放置上去,所以我们可以将Junit依赖设置为test范围的依赖。 还有server-api.jar, jsp-api.jar 是在代码编译的时候有用,但在布署项目的时候,是没有的用的,并且是不能把这个两个 jar包放置到tomcat里面的,因为tomcat里面有这两个jar包,会造成冲突, 所以一定要把这两个jar包给去掉,所以此时我们就要将它们设置为provided范围的依赖; 这就是依赖的范围。

依赖范围有六种: compile、test、provided、runtime、import和system, 其中import和system不常用,所以就不介绍了。maven的依赖范围用 <scope>元素表示的。

  • complie:编译依赖, 是默认的依赖范围。
    • 在src的main 目录下的代码可以访问这个范围下的依赖,即jar包。 src的test 目录下的代码也可以访问这个范围下的依赖。
    • 布署到 tomcat下时, 要把这个依赖放置在 WEB-INF 下的 lib 文件夹里面。
  • test:测试依赖,仅仅是测试使用的。
    • 在src的main目录下的代码不能访问这个依赖, src的test 目录下的代码可以访问这个依赖。
    • 布署到tomcat下时, 不会把这个依赖放置在 WEB-INF下的 lib文件夹里面。 如 junit 4.11这个依赖。
  • provided:提供依赖。
    • 在src的main目录下的代码可以访问这个依赖, src的test目录下的代码也可以访问这个依赖。
    • 布署到tomcat下时,不会把这个依赖放置在 WEB-INF 下的lib文件夹里面。 如 servlet-api, jsp-api
  • runtime:运行依赖。
    • src的main目录下的代码不能访问这个依赖, src的test目录下的代码可以访问这个依赖。
    • 布署到tomcat下时,会把这个依赖放置在 WEB-INF 下的lib 文件夹里面。 如 jdbc 驱动

总结成一张表就可以知道依赖范围表示的作用域如下:

在这里插入图片描述

四、依赖的传递

依赖的传递:如果我们的项目引用了一个Jar包,而该Jar包又引用了其它Jar包,那么此时的项目与引入的其它jar包就是传递依赖,但是在默认情况下项目编译时,Maven会把直接引用和间接引用的Jar包都下载到本地。

在这里插入图片描述
其中项目A直接依赖于B,而B也直接依赖于C,那么A就是间接依赖于C的,这就是传递性依赖。但是传递性依赖可能会产生依赖冲突。

如:项目A需要引入依赖包B,依赖包B又引入了依赖包C,那么B是A的直接依赖,C是A的传递依赖。如果项目A还需要引入依赖包D,依赖包D也引用了依赖包C,当依赖包B引用的C和依赖包D引用的C版本不同时,则发生了依赖冲突。

在这里插入图片描述

五、依赖的冲突

如果项目中引入了多个相同的jar,而jar包的版本不同就会产生依赖冲突。但是Maven有处理机制,Maven中采用了两种避免冲突的策略:①、短路径优先, ②、声明优先。因此在Maven中实际上是不存在依赖冲突的。

①、短路优先:就是哪个传递依赖离它最近则就优先选择它。

在这里插入图片描述
通过上面的图片我们可以分析出:

  • 项目A——>B——>C——>D
  • 项目A——>B——>D

第二条路径是离项目A最近的,也就是路径最短的。所以项目A最后用的是B依赖的D的jar包。

但是如果传递依赖的路径相同那么它们会怎么选择呢?我们接着往下看。

②、声明优先:就是谁先声明的就先选择谁。

在这里插入图片描述
通过上面的图片我们可以分析出:

  • 项目A——>B——>C
  • 项目A——>D——>C

可以发现它们的路径是一样的,那此时就是按照谁先声明的就选择谁。在pom.xml 的依赖 <dependencies> </dependencies> 里面, 先放置哪一个,就用哪一个。

你先放置B, 那么C就是B里面的, 你先放置D, 那么C就是D里面的。 所以在放置依赖时,有一定的顺序。

六、依赖的排除

依赖的排除:就是排除掉我们不想要的依赖。

比如:项目A依赖于B,B依赖于C,但有可能C依赖是不兼容的,会对项目造成一定的影响。此时,我们可以使用关键字exclusion排除C依赖。我们以spring-aop依赖为例:spring-aop是依赖于spring-beans和spring-core的。

在这里插入图片描述
假如我们需要排除掉spring-beans:

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>5.2.6.RELEASE</version>
  <exclusions>
    <exclusion>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
    </exclusion>
  </exclusions>
</dependency>

在这里插入图片描述

七、依赖的版本锁定与版本常量

版本的管理

在Maven中对依赖版本的管理暂且有两种方式:

  • 版本锁定
  • 版本常量

注意:这两种方式在实际的开发中会经常使用

版本锁定

版本锁定:指的是锁定项目中依赖的版本。这种是目前实际项目中使用的最多的。版本锁定需要使用到 dependencyManagement 元素。

需要说明的是dependencyManagement仅仅起到指锁定依赖版本的作用,其它的作用它什么都没有。而真正依赖包的下载任然要定义在dependencie元素中。

<!--锁定依赖的版本,它仅仅是起到锁定作用,不下载依赖包-->
<dependencyManagement>
  <dependencies>
	<!-- Junit单元测试依赖 -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
      
    <!-- Spring aop的相关依赖 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.6.RELEASE</version>
    </dependency>
  </dependencies>
</dependencyManagement>
 
<!--依赖包的下载仍然由dependencies管理-->
<!--只有dependencies中定义了依赖才会下载jar包-->
<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <!--<version>4.11</version>-->
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <!--<version>4.1.6.RELEASE</version>-->
  </dependency>
</dependencies>

注意: 当我们使用dependencyManagement元素来锁定依赖版本后,dependencie元素中的依赖版本(version)可写,也可不写,这样依赖版本引入则就有两种不同的方式了:

  • 在dependencies中的依赖中如果没有声明依赖的版本,就到dependenciesManage中去找,找到就使用锁定的版本号,没有就报错。
  • 在dependencies中声明了依赖的版本,则使用该依赖的版本,不管在dependenciesManage中有没有声明依赖的版本,都以dependencies中声明的版本为主。

dependencies和dependencyManagement的区别

  • dependencies是真正引入依赖的元素。而且在子项目中不写该依赖项,那么子项目仍然会从父项目中继承该依赖项(全部继承)。

    比如, 父pom中, dependencies中引入了lombok依赖, 并指定了版本, 子pom中就可以不引入lombok依赖了

  • dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显示的在dependencies中声明需要用的依赖。
    如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom;另外如果子项目中指定了版本号,那么会使用子项目中指定的依赖版本。

    父pom中, dependencyManagement里声明了lombok依赖,并指定了版本; 子pom中还是需要在dependencies中引入lombok依赖的,但不再需要指定版本了,按父pom中dm中的版本, 也可以自己指定版本,这样以,自己的版本为主

版本常量

版本常量是在pom.xml文件中提取出各自依赖的版本常量。封装至properties元素中,然后在依赖的version元素中用OGNL表达式获取即可,版本常量方式用的也很多。

这种方式方便了项目版本依赖管理的统一和后面的升级,而且它还具有继承性,在父项目pom.xml文件中定义的版本常量,在子模块中的pom.xml文件也能使用。

<!--定义版本常量-->
<properties>
  <spring.version>5.2.6.RELEASE</spring.version>
  <junit.version>4.11</junit.version>
</properties>
 
<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <!--使用版本常量-->
    <version>${junit.version}</version>
    <scope>test</scope>
  </dependency>
 
  <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
  </dependency>
  <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
  </dependency>
</dependencies>

八、继承和聚合

继承

Maven的继承,就是子模块会自动继承父模块中定义的一些依赖、插件、属性等等。

使用继承的目的是为了消除重复性,实际项目中最常用的是把子模块pom中很多相同的依赖配置提取出来,统一锁定在父模块的pom中。

如:grouptId、artifactId、version等等。然后在使用的时候子模块会直接继承父模块的依赖版本号,子模块中不再需要指定具体版本号,方便统一管理项目的依赖问题。

在这里插入图片描述

继承使用到的元素是<parent>。对于继承关系的父模块pom来说,它不知道自己被哪些子模块继承了,而对于子模块的pom来说,它必须知道自己的父模块pom是谁。

所以我们必须要在每个子模块的pom文件中添加指向父模块的引用,如下所示。

<parent>
    <artifactId>Parent</artifactId>
    <groupId>com.lucky</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>

还有要注意的是父模块坐标中的packaging必须是pom类型,否则子模块就不会继承。

<groupId>com.lucky</groupId>
<artifactId>Parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>

Maven继承的实际使用举例:

在这里插入图片描述
1)在父模块pom中设置依赖管理:

<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
 
    <groupId>com.thr</groupId>
    <artifactId>Parent</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
 
    <!-- 集中定义依赖版本号 -->
    <properties>
        <spring.version>5.2.6.RELEASE</spring.version>
        <junit.version>4.11</junit.version>
    </properties>
 
    <!-- 版本锁定,当子模块中有需要并且自行添加了具体依赖后才有效,
    它仅仅是起到锁定作用,不下载依赖包 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
 
            <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>${spring.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
 
</project>

2)在子模块中的pom设置需要的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<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">
    <!--父模块的引用-->
    <parent>
        <artifactId>Parent</artifactId>
        <groupId>com.thr</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
 
    <artifactId>Child1</artifactId>
 
    <!--依赖包的下载仍然有dependencies管理-->
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <!-- 版本号由父模块里面统一指定不再需要特别指定 -->
            <!--<version>4.11</version>-->
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
        </dependency>
    </dependencies>
</project>
  • groupId:项目组ID,项目坐标的核心元素。
  • version:项目版本,项目坐标的核心因素。
  • description:项目的描述信息。
  • organization:项目的组织信息。
  • inceptionYear:项目的创始年份。
  • url:项目的URL地址。
  • developers:项目的开发者信息。
  • contributors:项目的贡献者信息。
  • distributionManagement:项目的部署配置。
  • issueManagement:项目的缺陷跟踪系统信息。
  • ciManagement:项目的持续集成系统信息。
  • scm:项目的版本控制系统。
  • malilingLists:项目的邮件列表信息。
  • properties:自定义的Maven属性。
  • dependencies:项目的依赖配置。
  • dependencyManagement:项目的依赖管理配置。
  • repositories:项目的仓库配置。
  • build:包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等。
  • reporting:包括项目的报告输出目录配置、报告插件配置等。

聚合

Maven的聚合,就是用多个子模块聚合成一个整体模块,它们是整体与部分的关系,通常在定义一个整体后,再去分析这个整体的组成结构。例如一辆汽车是由轮胎、发动机、底盘、电器设备等等聚合而成。

在这里插入图片描述
聚合使用到的元素是<module>。对于 聚合模块(父模块) 来说,它知道有哪些被聚合的模块,而对于被聚合的模块来说,它们不知道被谁聚合了,也不知道它的存在。所以我们要在聚合模块中添加指向被聚合模块的引用,如下。

<modules>
    <module>../被聚合模块1</module>
    <module>../被聚合模块2</module>
    <module>../被聚合模块3</module>
</modules>

注意:module中的路径为相对路径。

例如前面继承例子中的Child1、Child2和Child3这三个模块,我们对它进行聚合。

<!--在父模块中聚合-->
<modules>
    <module>Child1</module>
    <module>Child2</module>
    <module>Child3</module>
</modules>

在这里插入图片描述

在实际的开发中,我们通常会将聚合和继承合二为一,也就是合在一起使用。但是聚合和继承是两个不同的概念,它们二者之间是没有什么关系的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

white camel

感谢支持~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值