Maven依赖管理(三)

首先通过maven交互式命令创建一个项目进行讲解,groupId一般为公司或者组织域名的倒排,如 com.example.course,artifactId为项目工程名,如course-demo01。

mvn archetype:generate

创建完成后查看项目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">

添加依赖

如现在需要将google的guava包添加到项目中

  • 获取guava包的GAV坐标
    使用关键字guava去远程仓库或者maven仓库 (https://mvnrepository.com/) 进行搜索,尽量选择 一个下载使用比较多的版本,注意查看包的漏洞风险,复制maven坐标。 guava

  • 添加依赖到项目pom
    复制guava包的GAV坐标,将其添加到pom.xml中的dependences标签中。

<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
  </dependency>
</dependencies>
  • 下载依赖包
    修改pom文件后,开发工具如idea等通常会自动去下载依赖包到本地仓库,也可以手动点击刷新, 或者在终端中执行mvn compile等命令主动触发maven去下载依赖包,下载完成后maven将该依 赖包自动添加到项目classpath中。

依赖范围

依赖范围主要用来控制项目在编译、运行或测试时,某个依赖包是否出现在classpath中,或者限制依 赖传递性等,通过pom.xml中的dependencies/dependency/scope标签来配置依赖范围。

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

scope有以下六种类型值

scope 描述
compile 默认值,依赖项在编译、测试和运行时都可用。
provided 依赖项由JDK或某些应用服务器(如tomcat)提供,在编译和测试期间需要依赖项,但在运行时不需要,不可传递。例如,servlet-api就是一个提供作用域的例子,线上tomcat服务器自带该依赖包。
runtime 在编译时不需要依赖项,但在运行或者测试时需要,该依赖出现在运行时和测试时的classpath中。
test 在测试范围内编译和执行测试时可用,而不在项目编译和运行classpath中,不可传递,例如,JUnit依赖项。
system 类似于provided作用域,但需要显式指定依赖项的路径,应尽量避免使用,该依赖不在仓库中进行管理。
import 该类型只能在pom.xml中的dependencyManagement标签中使用,相当于将某个类型为pom项目的全部依赖dependency都复制过来,因为POM跟Java类似都是单继承,然后通过import来实现增强。

import使用示例

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.example.course</groupId>
      <artifactId>course-parent</artifactId>
      <version>1.0</version>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

scope范围依赖项在生命周期不同阶段的classpath列表

guava

如项目中的scope为test的依赖包junit,只在src/main/test目录下生效,在src/main/java目录下的包 引用junit中Test注解无法通过编译。

package com.example.course;

import org.junit.Test;

public class App {
    @Test
    public void test() {

    }
    public static void main( String[] args ) {
        System.out.println( "Hello World!" );
   }
}

idea中直接编译错误,通过maven命令执行也是编译失败。

# mvn compile
...
[ERROR] COMPILATION ERROR :
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.676 s
[INFO] Finished at: 2023-05-09T09:34:00+08:00
[INFO] Final Memory: 13M/209M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compilerplugin:
3.1:compile (default-compile) on project course-demo01: Compilation
failure
[ERROR] /D:/campus/workspace/coursedemo01/
src/main/java/com/example/course/App.java:[3,17] 程序包org.junit不存在
[ERROR] /D:/campus/workspace/coursedemo01/
src/main/java/com/example/course/App.java:[10,6] 找不到符号
...

将其scope改为compile,刷新一下maven配置,则可以编译成功。这不难理解,如java编译命令

javac -d target/class 
-classpath "D:\maven\respoistory\junit\junit-4.12.jar"  
src/main/java/com/example/course/*.java

当junit包的依赖范围为compile时,maven在编译src/main/java源代码时则把它加入到classpath中,Java编译运行基础请参考:一篇文章让你秒懂Java运行基础

依赖传递

项目中A依赖B,B依赖C,那A中是否可以直接使用C包?

依赖传递

在A中是否可以直接使用C包,取决于B对依赖C配置的scope以及A对依赖B配置的scope。 创建项目course-demo02充当B角色,在course-demo02中添加4个依赖包,4个依赖包的scope分别不同,这个4个依赖包充当C角色。

<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.example.course</groupId>
  <artifactId>course-demo02</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>
  <properties>
    <!-- maven compiler插件使用 -->
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <!-- 设定文件读写编码,maven resource处理插件使用 -->
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.1</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>18.0</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
</project>

查看course-demo02依赖树图

# mvn dependency:tree
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ course-demo02 ---
[INFO] com.example.course:course-demo02:jar:1.0
[INFO] +- junit:junit:jar:3.8.1:test
[INFO] +- org.apache.commons:commons-lang3:jar:3.1:runtime
[INFO] +- com.google.guava:guava:jar:18.0:compile
[INFO] \- javax.servlet:javax.servlet-api:jar:3.1.0:provided

依赖传递

执行命令安装到本地仓库

# mvn clean install

将上面的course-demo01项目充当A角色,清空其全部依赖后,添加依赖包course-demo02-1.0.jar

<groupId>com.example.course</groupId>
<artifactId>course-demo01</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
  <dependency>
    <groupId>com.example.course</groupId>
    <artifactId>course-demo02</artifactId>
    <version>1.0</version>
  </dependency>
</dependencies>
  • course-demo02scope默认是compile,查看course-demo01依赖树图
# mvn dependency:tree
[INFO] com.example.course:course-demo01:jar:1.0-SNAPSHOT
[INFO] \- com.example.course:course-demo02:jar:1.0:compile
[INFO] +- org.apache.commons:commons-lang3:jar:3.1:runtime
[INFO] \- com.google.guava:guava:jar:18.0:compile

依赖传递

  • 将course-demo02 scope设置为test
# mvn dependency:tree
[INFO] com.example.course:course-demo01:jar:1.0-SNAPSHOT
[INFO] \- com.example.course:course-demo02:jar:1.0:test
[INFO] +- org.apache.commons:commons-lang3:jar:3.1:test
[INFO] \- com.google.guava:guava:jar:18.0:test

依赖传递

  • 将course-demo02 scope设置为runtime
# mvn dependency:tree
[INFO] com.example.course:course-demo01:jar:1.0-SNAPSHOT
[INFO] \- com.example.course:course-demo02:jar:1.0:runtime
[INFO] +- org.apache.commons:commons-lang3:jar:3.1:runtime
[INFO] \- com.google.guava:guava:jar:18.0:runtime

依赖传递

  • 将course-demo02 scope设置为provided
mvn dependency:tree
[INFO] com.example.course:course-demo01:jar:1.0-SNAPSHOT
[INFO] \- com.example.course:course-demo02:jar:1.0:provided
[INFO] +- org.apache.commons:commons-lang3:jar:3.1:provided
[INFO] \- com.google.guava:guava:jar:18.0:provided

依赖传递

结论:scope为test、provided的包不会传递,其它scope类型的包会传递,传递后的scope跟直接引 用包的scope有关系。

scope范围依赖传递如下图

依赖传递

备注: 横向第一排是C包的scope,纵向第一列是B包在A中的scope

maven提供了依赖传递的特性,但是没有层级深度的限制,项目最终的依赖树图会非常庞大,可以通过如下几种方式控制树图。

  • 通过scope可以限制依赖传递
  • 通过配置optional属性,B依赖C,同时B将C设置为optional,此时A依赖B时,不会引入B的可选依赖

将course-demo02的依赖junit、commons-lang3、servlet-api的optional设置为true,guava依赖不设置,默认为发false,再次将course-demo02 install到本地仓库。

<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>3.8.1</version>
    <scope>test</scope>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.1</version>
    <scope>runtime</scope>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
  </dependency>
  <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
    <optional>true</optional>
  </dependency>
</dependencies>

此时查看course-demo01的依赖树图,course-demo02只引入了guava包。

# mvn dependency:tree
[INFO] com.example.course:course-demo01:jar:1.0-SNAPSHOT
[INFO] \- com.example.course:course-demo02:jar:1.0:compile
[INFO] \- com.google.guava:guava:jar:18.0:compile

依赖传递

optional属性并不会干扰包传递后的scope,只是多了一层包传递的筛选控制。

依赖去重

依赖去重

项目A引用B与D,项目B引用C,C又引用D,由于maven的依赖传递,项目A中就可能出现两个版本不 同的D,maven提供了一些机制自动解决依赖重复,但并不能解决所有场景,有时需要开发人员在 pom中进行明确处理。

版本仲裁

当某个依赖包在项目中存在多个不同版本时,maven通过就近原则确定使用哪个版本,就近原则指的 是依赖树图中的层级关系。

依赖去重

项目A中,依赖B,B依赖C,C依赖D 2.0版本, A->B->C->D 2.0, 项目A同时引入了E, E依赖D 1.0版本,A->E->D 1.0, 根据就近原则,maven最终选择D 1.0引入到A中,如果层级一样,根据pom中声明的先后顺序,选择最早声明的版本。

course-demo02引入guava-18.0.jar,创建个项目course-demo03引入guava-19.0.jar。 依赖去重

在course-demo01中添加course-demo02、course-demo03依赖

<dependencies>
  <dependency>
    <groupId>com.example.course</groupId>
    <artifactId>course-demo02</artifactId>
    <version>1.0</version>
  </dependency>
  <dependency>
    <groupId>com.example.course</groupId>
    <artifactId>course-demo03</artifactId>
    <version>1.0</version>
  </dependency>
</dependencies>

上面两个依赖引入的guava包路径长度相同,按声明的先后顺序,最终项目中引入的是guava- 18.0.jar。

# mvn dependency:tree
[INFO] com.example.course:course-demo01:jar:1.0-SNAPSHOT
[INFO] +- com.example.course:course-demo02:jar:1.0:compile
[INFO] | \- com.google.guava:guava:jar:18.0:compile
[INFO] \- com.example.course:course-demo03:jar:1.0:compile

调整course-demo03与course-demo02声明顺序

<dependencies>
  <dependency>
    <groupId>com.example.course</groupId>
    <artifactId>course-demo03</artifactId>
    <version>1.0</version>
  </dependency>
  <dependency>
    <groupId>com.example.course</groupId>
    <artifactId>course-demo02</artifactId>
    <version>1.0</version>
  </dependency>
</dependencies>

再次查看项目course-demo01依赖树图,最终引入的是guava-19.0.jar。

# mvn dependency:tree
[INFO] com.example.course:course-demo01:jar:1.0-SNAPSHOT
[INFO] +- com.example.course:course-demo03:jar:1.0:compile
[INFO] | \- com.google.guava:guava:jar:19.0:compile
[INFO] \- com.example.course:course-demo02:jar:1.0:compile

这种通过声明先后顺序的方式,虽然能解决问题,但是存在不稳定性,并且每个包引入了其它很多 包,改一下顺序,某些包的版本均发生变化了,最简单直接的方法就是使用最短路径,直接在项目 dependency中声明依赖。

<dependencies>
  <dependency>
    <groupId>com.example.course</groupId>
    <artifactId>course-demo03</artifactId>
    <version>1.0</version>
  </dependency>
  <dependency>
    <groupId>com.example.course</groupId>
    <artifactId>course-demo02</artifactId>
    <version>1.0</version>
  </dependency>
  <dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
  </dependency>
</dependencies>

maven根据就近原则,最终项目选择guava-18.0.jar,不依赖声明的先后循序。

依赖去重

# mvn dependency:tree
[INFO] com.example.course:course-demo01:jar:1.0-SNAPSHOT
[INFO] +- com.example.course:course-demo03:jar:1.0:compile
[INFO] +- com.example.course:course-demo02:jar:1.0:compile
[INFO] \- com.google.guava:guava:jar:18.0:compile

统一版本

  • 在maven中的依赖包尽量要明确版本,A依赖B,B依赖C,在A中使用的是B传递来的C,后续B降低了C版本,可能导致A项目编译错误。随着项目中的依赖逐渐庞大,版本仲裁的潜在风险也越大。
  • 采用最短路径方式,在pom.xml中通过dependency标签直接声明依赖虽然可以解决问题,但是会导致项目的依赖树层次关系比较混乱,dependencies标签中尽量用于声明项目直接使用的依赖,而不是为了解决版本问题。
  • maven提供了另外一种方式dependencyManagement统一管理传递进来的依赖版本。

修改course-demo01的pom.xml配置,将guava的依赖配置到dependencyManagement标签中。

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>18.0</version>
    </dependency>
  </dependencies>
</dependencyManagement>
<dependencies>
  <dependency>
    <groupId>com.example.course</groupId>
    <artifactId>course-demo03</artifactId>
    <version>1.0</version>
  </dependency>
  <dependency>
    <groupId>com.example.course</groupId>
    <artifactId>course-demo02</artifactId>
    <version>1.0</version>
  </dependency>
</dependencies>

查看依赖树图,注意guava包引入路径

# mvn dependency:tree
[INFO] com.example.course:course-demo01:jar:1.0-SNAPSHOT
[INFO] +- com.example.course:course-demo03:jar:1.0:compile
[INFO] | \- com.google.guava:guava:jar:18.0:compile
[INFO] \- com.example.course:course-demo02:jar:1.0:compile

guava包在树图中上一级是course-demo03,而course-demo03中添加的是guava-19.0.jar,这就是 dependencyManagement的统一版本管理,如果course-demo02声明的顺序在course-demo03上 面,那guava-18.0.jar引入路径则在course-demo02下。

现在将spring-beans添加到course-demo01的dependencyManagement标签中,保存配置后通过命 令再次查看依赖树图。

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>18.0</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>5.0.7.RELEASE</version>
    </dependency>
  </dependencies>
</dependencyManagement>
<dependencies>
  <dependency>
    <groupId>com.example.course</groupId>
    <artifactId>course-demo03</artifactId>
    <version>1.0</version>
  </dependency>
  <dependency>
    <groupId>com.example.course</groupId>
    <artifactId>course-demo02</artifactId>
    <version>1.0</version>
  </dependency>
</dependencies>
# mvn dependency:tree
[INFO] com.example.course:course-demo01:jar:1.0-SNAPSHOT
[INFO] +- com.example.course:course-demo03:jar:1.0:compile
[INFO] | \- `com.google.guava:guava:jar:18.0:compile`
[INFO] \- com.example.course:course-demo02:jar:1.0:compile

依赖树图没有任何变化,spring-beans包并没有添加到项目中。

结论:dependencyManagement配置的依赖,并不会一定引入到项目中,它只是用来管理传递进来 的依赖版本,推荐使用dependencyManagement标签来解决依赖传递。

思考下面这段臃肿重复的配置,maven最终选择添加哪个版本的guava包?

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>18.0</version>
    </dependency>
  </dependencies>
</dependencyManagement>
<dependencies>
  <dependency>
    <!-- 这个包中引入了guava-19.0.jar -->
    <groupId>com.example.course</groupId>
    <artifactId>course-demo03</artifactId>
    <version>1.0</version>
  </dependency>
  <dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>17.0</version>
  </dependency>
</dependencies>

主动排除

使用dependencyManagement来管理依赖传递性,传递的jar全部会引入到项目中,但是有些传递的 依赖包在项目中明确不会使用,则可以采用主动排除方式,在pom.xml中通过exclusion明确去除传递 进来的依赖。

如在course-demo01中直接明确排除course-demo02的guava依赖。

<dependency>
  <groupId>com.example.course</groupId>
  <artifactId>course-demo02</artifactId>
  <version>1.0</version>
  <exclusions>
    <exclusion>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
    </exclusion>
  </exclusions>
</dependency>

也可以将course-demo02的全部依赖去除

<dependency>
  <groupId>com.example.course</groupId>
  <artifactId>course-demo02</artifactId>
  <version>1.0</version>
  <exclusions>
    <exclusion>
      <groupId>*</groupId>
      <artifactId>*</artifactId>
    </exclusion>
  </exclusions>
</dependency>

项目开发完成后,尽量去除未使用的相关依赖包,减小项目包占用的空间,可通过一些辅助命令分析依赖。

# mvn dependency:analyze

依赖继承

  • 在Maven中POM是可以继承的,类似Java只能单继承,A项目继承B项目,实际上指的是A项目的POM继承了B项目POM中的配置内容,父工程的作用经常用于统一管理依赖信息的版本。
  • 常见的场景就是大型项目的模块拆分、如以SpringCloud、SpingBoot构建的微服务项目最典型。如果每个项目都单独引入依赖,可能导致各项目依赖版本不统一,后期维护升级非常麻烦,也存在兼容性问题。
  • 父工程必须是pom类型的Maven项目,即打包类型(packaging)必须是pom。

packaging打包类型有如下常用的几种方式

类型 描述
jar 默认值,普通jar包
war web服务器(如tomcat) war包
pom 作为父工程,管理其它项目的依赖
maven-plugin maven插件类型

maven会根据项目pom中配置的packaging类型,在生命周期的阶段绑定不同的目标。比如现在需要使用springboot开发很多项目,而springboot依赖很多包,这些包又都必须保持在同一个版本才能兼容使用。

创建父项目

通过命令mvn archetype:generate创建maven项目course-web-parent,并将其packaging标签值改 为pom

<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.example.course</groupId>
  <artifactId>course-web-parent</artifactId>
  <version>1.0</version>
  <packaging>pom</packaging>
  <properties>
    <!-- maven compiler插件使用 -->
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <!-- 设定文件读写编码,maven resource处理插件使用 -->
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
</project>

添加开发springboot web应用所需的依赖包添加到dependencyManagement中

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>5.0.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.0.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.0.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.0.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.0.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.0.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.0.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>
  </dependencies>
</dependencyManagement>

执行mvn install命令安装到本地仓库中,最终的包是course-web-parent-1.0.pom

# mvn clean install
...
[INFO] --- maven-install-plugin:2.4:install (default-install) @ course-webparent ---
[INFO] Installing D:\campus\workspace\course-web-parent\pom.xml to
D:\campus\m2\repo\com\example\course\course-web-parent\1.0\course-web-parent-
1.0.pom
...

dependencyManagement中配置的依赖并不会直接引入到项目中,pom类型项目是用来管理依赖信 息的,因此通过命令mvn dependency:tree查看不会有任何依赖树图。

创建子项目

通过命令mvn archetype:generate创建maven项目course-web,通过parent标签继承courseweb-parent项目。

<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>
  <parent>
    <groupId>com.example.course</groupId>
    <artifactId>course-web-parent</artifactId>
    <version>1.0</version>
  </parent>
  <!-- 继承父项目,直接用父项目的groupId -->
  <!-- <groupId>com.example.course</groupId> -->
  <artifactId>course-web</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>
  <properties>
    <!-- maven compiler插件使用 -->
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <!-- 设定文件读写编码,maven resource处理插件使用 -->
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
</project>

course-web项目默认将父项目pom.xml中的groupId、dependencyManagement等配置信息继承, 实际并不会将任何依赖引入到项目中,通过mvn help:effective-pom命令查看course-web生 效的pom信息,最终生效的pom配置来自于超级pom、父pom、当前pom的合并。

前面提到scope范围import的作用,也可以使用import导入父配置,解决了pom只能单继承问题。

<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.example.course</groupId>
  <artifactId>course-web</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.example.course</groupId>
        <artifactId>course-web-parent</artifactId>
        <version>1.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

import只能将某项目的dependencyManagement配置信息导入到本项目的dependencyManagement,而通过parent继承方式,可以获得父项目pom中的绝大部分配置,具体配置项请参阅Maven官网。 使用parent继承父项目后,导入父项目中定义的依赖时不用再填写具体版本号,默认使用父项目中定义的版本,如spring-beans包,即5.0.7.RELEASE,这样多个子项目使用的版本保持一致。

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

有时父项目中的某个依赖定义有兼容性问题,子项目course-web也可以指定spring-beans具体的版本 号进行覆盖。

聚合

通常在项目中可以根据业务或者功能将项目拆分为多个小模块进行开发,每个模块对应的就是maven中的一个module。在maven中,一个工程聚合多个modules,在项目下执行maven命令时,maven会先在各module中先执行。 现在开发一个项目course,可能按照功能定义如下模块

  • course-api 提供对外访问的http api
  • course-mq 处理系统收发mq消息的模块
  • course-db 操作数据库的模块
  • course-core 核心业务逻辑模块

创建项目骨架

通过命令 mvn archetype:generate 创建course项目,packaging必须为pom

<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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example.course</groupId>
  <artifactId>course</artifactId>
  <packaging>pom</packaging>
  <version>1.0-SNAPSHOT</version>
</project>

创建module

在项目course根目录下通过命令mvn archetype:generate分别创建四个子项目course-apicoursedbcourse-mqcourse-db,maven自动将每个子项目作为module添加到了course项目的pom.xml中。

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.example.course</groupId>
    <artifactId>course</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <artifactId>course-core</artifactId>
  <version>1.0-SNAPSHOT</version>
</project>

根据业务场景,核心业务实现模块course-core引用course-db以及course-mq,course-api引用course-core对外提供api服务。 聚合

course-api引用核心业务模块course-core

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.example.course</groupId>
    <artifactId>course</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <artifactId>course-api</artifactId>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>com.example.course</groupId>
      <artifactId>course-core</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
</project>

核心业务模块course-core引用消息处理模块course-mq、以及数据库操作模块course-db

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.example.course</groupId>
    <artifactId>course</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <artifactId>course-core</artifactId>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>com.example.course</groupId>
      <artifactId>course-db</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>com.example.course</groupId>
      <artifactId>course-mq</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
</project>

在course根目录下执行安装命令 mvn clean install,它会根据依赖关系决定优先构建哪个模块,而不 是根据pom.xml中定义的modules顺序构建。

$ mvn clean install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] course
[INFO] course-db
[INFO] course-mq
[INFO] course-core
[INFO] course-api
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building course 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ course ---
[INFO]
[INFO] --- maven-install-plugin:2.4:install (default-install) @ course ---
[INFO] Installing D:\campus\workspace\course\pom.xml to
D:\maven\respoistory\com\example\course\course\1.0-SNAPSHOT\course-1.0-
SNAPSHOT.pom
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building course-db 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
...
[INFO] ------------------------------------------------------------------------
[INFO] Building course-mq 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] ...
[INFO] ------------------------------------------------------------------------
[INFO] Building course-core 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] ...
[INFO] ------------------------------------------------------------------------
[INFO] Building course-api 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] course ............................................. SUCCESS [ 0.499 s]
[INFO] course-db .......................................... SUCCESS [ 1.268 s]
[INFO] course-mq .......................................... SUCCESS [ 0.226 s]
[INFO] course-core ........................................ SUCCESS [ 0.125 s]
[INFO] course-api ......................................... SUCCESS [ 0.076 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------