Maven 核心概念

POM 文件

POM(Project Object Model) 全名为项目对象模型,定义了项目的基本信息,用于描述项目如何构建、声明项目依赖等等。

项目坐标

<?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.cqeips.springcloud</groupId>
  <artifactId>springcloud-starter</artifactId>
	<version>1.0.0</version>
  <packaging>jar</packaging>
	<name>springcloud-starter</name>
	<description>Spring cloud starter</description>
</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
  • xml 定义 XML 文件的版本和编码方式
  • project pom 文件的根元素,以及命名空间等
  • modelVersion pom 模型的版本,Maven2 、Maven3 来说,必须为4.0.0
  • groupId 项目组,通常采用反域名格式,一般不定义到组织级别,应该定义到项目级别,如 com.cqeips.springcloud
  • artifactId 当前Maven项目组中唯一ID
  • version 当前版本号
  • packaging 打包方式,默认为 jar
  • name 项目名称
  • description 项目描述

其中 groupId、artifactId、version 是必选内容,这三个参数确定了项目的位置坐标,其他参数可选。

项目依赖 dependencies

  <dependencies>
    <dependency>
      <groupId>...</groupId>
      <artifactId>...</artifactId>
      <version>...</version>
      <type>...</type>
      <scope>...</scope>
      <optional>...</optional>
      <exclusions>
        <exclusion>...</exclusion>
      </exclusions>
    </dependency>
  </dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
  • dependencies 该元素描述了项目相关的所有依赖。可以包含一个或多个dependecy。
  • dependency 单个依赖
  • type 依赖类型,对应项目坐标定义的packaging。
  • scope 依赖范围。
  • optional 依赖是否可选。
  • exclusions 用于排除传递性依赖

依赖范围scope

用来控制依赖与classpath(编译classpath、测试classpath、运行classpath)的关系,maven的有以下几种依赖范围:

  1. complile: 编译依赖范围。默认使用该依赖范围,对于编译、运行、测试三种classpath都有效。如:spring-core。
  2. test: 测试依赖范围。只对于测试classpath有效。如:junit。
  3. provided: 已提供依赖范围。对于编译和测试classpath有效,对于运行时无效。在运行时一般需要容器进行提供,不需要Maven重复引入。如sevlet-api。
  4. runtime: 运行时依赖范围。对于测试和运行classpath有效,对于编译主代码时无效。如JDBC驱动实现。
  5. system: 系统依赖范围。对于编译和测试classpath有效,对于运行时无效。需要通过systemPath显示指定依赖文件的路径。由于此依赖范围不是由Maven仓库解析的,而是与本机文件系统绑定,可能造成构建的不可移植,需谨慎使用。systemPath元素可以引用环境变量,如:
<dependency>
  <groupId>javax.sql</groupId>
  <artifactId>jdbc-stdext</artifactId>
  <version>2.0</version>
  <scope>system</scope>
  <systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>
1
2
3
4
5
6
7
  1. import: 导入依赖范围。该依赖不会对三种classpath产生影响。

传递性依赖

所谓传递性依赖指所依赖的包间接产生的依赖。如引入spring-core,会间接引入common-logging包。传递性依赖范围由直接依赖和间接依赖范围共同决定,依赖关系如下表所示:

左边为直接依赖,顶部为间接依赖。

compile test provided runtime
compile compile —— —— runtime
test test —— —— test
provided provided —— provided provided
runtime runtime —— —— runtime

依赖调节

如果在项目中引入多个依赖包,可能造成传递性依赖问题。这时Maven遵循以下两个原则:

  1. 路径最短原则
    如有两条依赖路径:
    • A>B>C>X(1.0)
    • A>D>X(2.0) 则X(2.0)优先解析使用。
  2. 顺序靠前原则
    当路径长度相同时,谁靠前引入,谁优先使用。

可选依赖

不推荐使用! 某一个项目B实现了多个特性,其中特性一依赖于X,特性二依赖于Y,而且这两个特性是互斥的,用户不可能同时选择这两个特性。如果项目A依赖于项目B,则X、Y对于项目A来说则为可选依赖。

排除依赖

看下面的例子

<groupId>xyz.yxguang.mvn.demo</groupId>
<artifactId>project-a</artifactId>
<version>1.0.0</version>
<dependencies>
  <dependency>
    <groupId>xyz.yxguang.mvn.demo</groupId>
    <artifactId>project-b</artifactId>
    <version>1.0.0</version>
    <exclusions>
      <exclusion>
        <groupId>xyz.yxguang.mvn.demo</groupId>
        <artifactId>project-c</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
  <dependency>
    <groupId>xyz.yxguang.mvn.demo</groupId>
    <artifactId>project-c</artifactId>
    <version>1.0.0</version>
  </dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

在项目A中引入项目B依赖,项目B又依赖于项目C,由于某种原因不想引入传递性依赖C,而是显示指定项目C的版本,这时可以使用exclusion排除传递性依赖C。

A------->B------>C(被排除)
|------->C(显示指定版本)
1
2

归类依赖

在引入依赖库时,我们可以将依赖的版本单独提取出来,进行统一管理,方面后续修改或升级。这是就需要使用properties元素。如:

<properties>
  <springframework.version>2.5.6</springframework.version>
</properties>
<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${springframework.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>${springframework.version}</version>
  </dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Maven 仓库

仓库的分类

Maven仓库分为本地仓库和远程仓库。当引入一个依赖时,首先会在本地仓库进行查找,如果本地仓库不存在,则会访问远程仓库进行查找,同时下载到本地仓库。如果远程仓库也不存在该依赖,则报错。其中远程仓库又可以分为中央仓库、私服和其他仓库。

                Maven仓库
                   |
      -----------------------------
      |                           |
  本地仓库                      远程仓库
                                  |
                 -------------------------------------
                 |                |                  |
             中央仓库             私服             其他仓库
1
2
3
4
5
6
7
8
9

本地仓库

默认情况下,Maven 默认仓库地址为~/.m2/repository,如果用户希望自定义本地仓库地址,可以通过修改~/.m2/settings.xml进行设置,修改localRepository元素为希望的地址:

<settings>
  <localRepository>D:\java\repository\</localRepositry>
</settings>
1
2
3

中央仓库

Maven至少必须配置一个可用的远程仓库,中央仓库就是这样一个默认的远程仓库。默认中央仓库的配置在%MAVEN_HOME%\lib\maven-model-builder-3.5.0.jar中:

<repositories>
 <repository>
    <id>central</id>
    <name>Central Repository</name>
    <url>https://repo.maven.apache.org/maven2</url>
    <layout>default</layout>
    <snapshots>
      <enabled>false</enabled>
    </snapshots>
  </repository>   
</repositories>
1
2
3
4
5
6
7
8
9
10
11

私服

详见搭建Maven私服

配置远程仓库

在某些情况下,默认的中央仓库无法满足项目需求,需要配置额外的远程仓库。这时需要在POM文件中进行仓库配置:

<repositories>
  <repository>
    <id>...</id>
    <name>...</name>
    <url>...</url>
    <release>
      <enable>true</enable>
      <updatePolicy>daily|never|always|interval:X</updatePolicy>
      <checkSumPolicy>...</checkSumPolicy>
    </release>
    <snapshots>
      <enable>false</enable>
    </snapshots>
    <layout>default</layout>
  </repository>
</repositories>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • id 仓库的唯一ID,相同的ID,仓库的配置会被覆盖
  • name 仓库名称
  • url 仓库地址
  • release 发布版本,enable为true表示开启发布版本下载
  • snapshot 快照版本,enable为true表示开启快照版本下载
  • layout 仓库布局,默认为Maven2或Maven3布局
  • updatePolicy 更新策略,daily每天更新;never从不更新;always每次构建都检查更新;interval:X 每X分钟检查更新
  • checkSumPolicy 检查校验和文件策略,warn/fail/ignore

远程仓库认证

有时为了安全方面考虑需要限制远程仓库的访问权限,需要对远程仓库配置认证信息。认证信息配置在setting.xml中:

<settings>
  ...
  <servers>
    <id>...</id>
    <username>...</username>
    <password>...</password>
  </servers>
</settings>
1
2
3
4
5
6
7
8

setting.xmlserver元素id 必须和 POM 文件中需要认证的repository保持一致。

部署至远程仓库

有时需要将第三方构建或内部构建部署到远程仓库中,需要对POM文件进行配置:

    <distributionManagement> 
        <repository> 
            <uniqueVersion /> 
            <id> banseon-maven2 </id> 
            <name> banseon maven2 </name> 
            <url> file://${basedir}/target/deploy </url> 
            <layout></layout> 
        </repository> 
        <snapshotRepository> 
            <uniqueVersion /> 
            <id> banseon-maven2 </id> 
            <name> Banseon-maven2 Snapshot Repository </name> 
            <url> scp://svn.baidu.com/banseon:/usr/local/maven-snapshot </url> 
            <layout></layout>
        </snapshotRepository> 
    </distributionManagement> 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

配置正确后在命令行运行mvn clean deploy进行部署。

配置仓库镜像

仓库镜像往往比中央仓库提供更快速、稳定的服务。比如阿里云镜像。配置镜像仓库代替中央仓库,修改settings.xml

<settings>
  <mirrors>
    <mirror>
      <id>aliyunmaven</id>
      <mirrorOf>central</mirrorOf>
      <name>阿里云公共仓库</name>
      <url>https://maven.aliyun.com/repository/public</url>
    </mirror> 
  </mirrors>
</settings>
1
2
3
4
5
6
7
8
9
10

该配置中mirrorOf值为central表示使用镜像仓库代替中央仓库,任何对于中央仓库的请求都会转到该仓库。mirrorOf值为*表示该镜像为所有仓库的镜像。
更多阿里云镜像请参考AliRepo
Maven 支持更高级的镜像配置:

  • <mirrorOf>*</mirrorOf> 匹配所有远程仓库
  • <mirrorOf>external:*</mirrorOf> 匹配所有远程仓库。使用localhostfile:// 协议除外。匹配所有非本机远程仓库。
  • <mirrorOf>repo1、repo2</mirrorOf> 匹配repo1和repo2,多个仓库使用,分割
  • <mirrorOf>*,!repo1</mirrorOf> 匹配所有远程仓库,repo1除外。

生命周期

Maven 拥有三套独立的生命周期,分别为cleandefaultsite,每套生命周期相互独立互不干扰。每套生命周期又分为多个阶段(phase),这些phase是有顺序的,后面的阶段依赖于前面的阶段。详见Maven Lifecycle Reference

clean生命周期

用于清理项目,包含三个阶段:

  1. pre-clean 执行清理前需要完成的工作
  2. clean 清理上次构建生成的文件
  3. post-clean 执行清理后需要完成的工作

default 生命周期

定义了构建时需要执行的所有步骤,其中包括的主要阶段如下:

  • validate
  • initialize
  • generate-sources
  • process-sources 处理项目主资源文件。一般来说指将src/main/resources目录内的内容进行变量替换等工作后,复制到项目输出的主classpath目录中。
  • generate-resources
  • process-resources
  • compile 编译项目的主源码。一般来说,是编译src/main/java目录下的Java文件至项目输出的主classpath目录中。
  • process-classes 处理项目测试资源文件。一般来说,是对src/main/resources目录内容进行变量替换等工作后,复制到项目输出的测试classpath目录中。
  • generate-test-sources
  • process-test-sources
  • generate-test-resources
  • process-test-resources
  • test-compile 编译项目的测试代码。一般来说,是编译src/test/java目录下的Java文件至项目输出的测试classpath目录中。
  • process-test-classes
  • test 使用单元测试框架运行测试,测试代码不会被打包或部署。
  • prepare-package
  • package 接受编译好的代码,打包成可发布的格式,如JAR。
  • pre-integration-test
  • integration-test
  • post-integration-test
  • verify
  • install 将包安装到Maven本地仓库,供本地其他Maven项目使用。
  • deploy 将最终的包部署到远程仓库,供其他开发人员和Maven项目使用。

site生命周期

用于建立和发布项目站点,主要生命周期如下:

  • pre-site 执行一些在项目站点之前需要完成的工作。
  • site 生成项目站点文档。
  • post-site 执行一些在生成项目站点之后需要完成的工作。
  • site-deploy 将生成的项目站点发布到服务器。

命令行与生命周期

  • mvn clean 调用 clean 生命周期的clean阶段。
  • mvn test 调用 default 生命周期的test阶段。实际执行会从validate阶段一直执行到test阶段。
  • mvn clean install 调用clean 生命周期clean阶段和default生命周期install阶段。
  • mvn clean deploy site-deploy 调用clean生命周期clean阶段,default生命周期deploy阶段,site生命周期site-deploy阶段

插件

插件往往与生命周期相互绑定,用于完成生命周期中定义的具体任务,插件以独立的构件形式存在。

插件目标

对于一个插件而言,往往拥有很多个功能,这些功能能够完成多个任务。如maven-dependency-plugin,能够分析依赖、列出项目依赖树、列出项目所有已解析的依赖等等。这些功能同时聚合在一个插件中,每个功能就是一个插件目标。上述几个功能对应的插件目标为:dependency:analyzedependency:treedependency:list,这种通用写法格式为:(插件前缀):(插件目标)。

插件绑定

插件需要与生命周期的各阶段绑定才能正常工作,插件的绑定分为内置绑定和自定义绑定两种,下面分别看看这两种绑定。

内置绑定

Maven生命周期的各阶段默认绑定了很多插件,当调用生命周期阶段的时候,对应的插件目标就会执行相应的任务。

  • clean生命周期内置绑定
生命周期 插件目标
pre-clean maven-clean-plugin:clean
clean
post-clean
  • site生命周期内置绑定
生命周期 插件目标
pre-site maven-site-plugin:site
site
post-site
site-deploy maven-site-plugin:deploy
  • default 生命周期内置绑定 default 生命周期的内置绑定相对于cleansite来说比较复杂。项目的打包类型会影响构建的过程,因此,default生命周期的阶段与插件目标的绑定关系由项目打包类型所决定。最常见的为jar打包类型,内置插件绑定关系如下:
生命周期 插件目标
process-resources maven-resources-plugin:resources
compile maven-compiler-plugin:compile
process-test-resources maven-resources-plugin:test-resources
test-compile maven-compiler-plugin:test-compile
test maven-surefire-plugin:test
package maven-jar-plugin:jar
install maven-install-plugin:install
deploy maven-deploy-plugin:deploy

对于以上的内容仅列出其中部分内置依赖关系,详细内容请参考Maven Built-in Lifecycle Bindings

自定义绑定

除了内置绑定外,用户还可以将某个插件目标绑定到生命周期的某个阶段。如下:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-source-plugin</artifactId>
      <version>2.1.1</version>
      <executions>
        <execution>
          <id>attach-sources</id>
          <pahse>verify</pahse>
          <goals>
            <goal>jar-no-fork</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • executions 配置多个执行任务
  • execution 配置单个执行任务
  • id 任务ID
  • phase 配置生命周期阶段,将任务与阶段进行绑定
  • goals 指定要执行的插件目标

很多插件目标在编写时已经定义了默认绑定的阶段,因此可以不用配置phase元素,可以通过maven-help-plugin查看更多信息。

插件配置

完成插件与生命周期绑定后,还可以配置插件目标的参数,进一步调整插件目标执行的任务。通常有三种插件配置方式:

  • 命令行插件参数配置 我们可以在命令行中使用-D 携带参数信息来配置插件目标参数,如:mvn install -Dmaven.test.skip=true ,maven-surefire-plugin 中提供了一个maven.test.skip参数,当值为true 的时候,就会跳过测试。
  • POM中插件全局配置 在声明插件的时候,对插件进行全局配置,此插件的所有目标任务都会使用这些配置。如:
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>2.1.2</version>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
      </configuration>
    </plugin>
  </plugins>
</build>
1
2
3
4
5
6
7
8
9
10
11
12
13
  • POM中插件任务配置 除了进行全局配置,用户可以为某个特定插件任务配置参数。如:
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-antrun-plugin</artifactId>
      <version>1.3</version>
      <excutions>
        <excution>
          <id>ant-validate</id>
          <phase>validate</phase>
          <goals>
            <goal>run<goal>
          </goals>
          <configuration>
            <tasks>
              <echo>I'm bound to validate phase.</echo>
            </tasks>
          </configuration>
        </excution>
      </excutions>
    </plugin>
  </plugins>
</build>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

聚合与继承

聚合

在多模块下,Maven可以通过聚合模块的方式实现多模块的构建、打包等工作。聚合模块的POM文件中packaging值必须为pom,否则无法完成构建。通常聚合模块的POM文件格式如下:

...
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<packaging>pom</packaging>
<name>...</name>
<modules>
  <module>module-a</moudule>
  <module>module-b</module>
</modules>
1
2
3
4
5
6
7
8
9
10
  • modules 用于引入多模块配置
  • module 子模块POM文件的相对目录

通常,模块所在的目录名称应当与其artifactId一致,如果用户将module-a项目放到a-module目录下,这时聚合模块的配置就要相应改成<module>a-module</module>

继承

POM的继承,用于将子模块POM文件中相同的公共配置提取到聚合模块中。

  1. 首先提取出公共或重复配置到父模块系统中,如下:
<groupId>xyz.yxguang.mvn.demo</groupId>
<artifactId>module-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>Module Parent</name>
1
2
3
4
5
  1. 配置子模块继承父模块,如下:
<parent>
  <groupId>xyz.yxguang.mvn.demo</groupId>
  <artifactId>module-parent</artifactId>
  <version>1.0.0</version>
  <relativePath></relativePath>
</parent>
<artifactId>module-a</artifactId>
<name>module-a</name>
1
2
3
4
5
6
7
8
  • relativePath 表示父元素POM文件的相对路径
  1. 聚合模块,如下
<modules>
  <module>module-a</module>
  <module>module-b</module>
</modules>
1
2
3
4

可继承的POM元素

  • groupId
  • version
  • description
  • orgnaization
  • inceptionYear
  • url
  • developers
  • contributors
  • distributionManagement
  • issueManagement
  • ciManagement
  • scm
  • mailingLists
  • properties
  • dependencies
  • dependencyManagement
  • repositories
  • build
  • reporting

依赖管理

dependenceis 与 dependencyManagement

  • dependencies
    支持依赖继承,如果子模块继承父模块,则子模块继承父模块所有dependencies依赖。如:
    module-parent 有四个依赖 dependency-1、dependency-2、dependency-3、dependency-4,如果module-a 继承module-parent 则,module-a 同时也会继承上述四个依赖。
    上述做法可行,但存在问题。如果现在有一个新模块module-b需要继承module-parent,但是仅需要继承dependency-1 依赖,那么使用dependencies 无法满足我们的要求。

  • dependencyManagement dependencyManagement能够让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性。dependencyManagement元素下的依赖声明不会引入实际的依赖,但是它能够管理子模块的依赖。

Last Updated: 5/22/2019, 5:42:53 PM