Maven基础

Maven 的坐标和依赖

坐标详解

依赖管理是 Maven 的一大功能,为了能够自动化地解析任何一个 Java 构件,Maven 就必须将它们唯一标识,这就是依赖管理的底层基础——坐标。Maven 的坐标是通过一些元素定义的,它们是 groupId、artifictId、version、packaging 和 classifier。先看一组坐标定义:

<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
<packaging>jar</packaging>

这是 JSON 框架 fastjson 的坐标定义。在代码片段中,其坐标为:groupId:com.alibaba、artifactId:fastjson、version:1.2.47、packaging:jar,没有指定 classifier。下面说明各个坐标元素:

  • groupId:定义当前 Maven 项目隶属的组织或实际项目。如果 Maven 项目没有划分成多个模板,那么 groupId  一般定义成 Maven 项目关联的组织或公司,如 fastjson 的 groupId 是 com.alibaba。如果 Maven 项目划分成多个模块,如 Apache Commons 包含了 commons-lang3、commons-io 等模块,那么 groupId 一般是定义成 Maven 项目隶属的实际项目,如 commons-lang3 的 groupId 是 org.apache.commons。

  • artifictId:定义项目在组中的唯一标识,一般是项目名或模块名。如果 Maven 项目没有划分成多个模板,那么 artifictId 一般定义成项目名,如 fastjson。如果 Maven 项目划分成多个模块,那么 artifictId 一般定义成模块名,如 commons-lang3。

  • version:定义 Maven 项目的版本号。

  • packaging:定义 Maven 项目的打包方式。打包方式通常与所生成构件的文件扩展名相对应,如上例中 packaging 为 jar,最终构件的文件名为 fastjson-1.2.47.jar。而使用 war 打包方式的 Maven 项目,最终生成的构件通常会有一个 .war 文件。打包方式还会影响到构建的生命周期,比如 jar 打包和 war 打包会使用不同的命令。如果没有定义 packaging 元素,则默认为 jar。

  • classifier:定义构建输出的一些附属构件。附属构件与主构件对应,如上例中的主构件是 fastjson-1.2.47.jar,该项目可能还会通过使用一些插件生成源代码和文档,如 fastjson-1.2.47-sources.jar、fastjson-1.2.47-javadoc.jar,这些就是附属构件。这样,附属构件也就拥有了自己唯一的坐标。注意,classifier 不能直接定义,因为附属构件不是项目直接默认生成的,而是由附加的插件帮助生成。

上述五个元素中,groupId、artifictId 和 version 是必须定义的, packaging 是可选的(默认为 jar),而 classifier 是不能直接定义的。

项目构件的文件名与坐标是相对应,一般规则为 artifictId-version[-classifier].packaging,[-classifier] 表示可选。例如上例 fastjson 的主构件为 fastjson-1.2.47.jar,附属构件有 fastjson-1.2.47-sources.jar。需要注意的一点是 packaging 并非一定与构件的扩展名对应,比如 packaging 为 maven-plugin 的构件扩展名为 jar。

 

依赖的配置

Maven 项目通过在 POM 配置 dependencies 元素来管理依赖,这样 Maven 就会根据配置自动下载所需的依赖构件, dependencies 可包含如下内容:

<project >
    ...
  <dependencies>
    <dependency>
      <groupId>...</groupId>
      <artifactId>...</artifactId>
      <version>...</version>
      <type>...</type>
      <scope>...</scope>
      <optional>...</optional>
      <exclusions>
        <exclusion>
          <groupId>...</groupId>
          <artifactId>...</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    </dependency>
    <dependency>
      ...
    </dependency>
  </dependencies>
    ...
</project>

dependencies 可以包含一系列 dependency 元素,每个 dependency 表示引入一个依赖,dependency 可包含的元素有:

  • groupId、artifactId、version:依赖的基本坐标。

  • type:依赖的类型,对应于项目坐标定义的 packaging。通常情况下,该元素不必声明,默认值为 jar。

  • scope:依赖范围。

  • optional:标记依赖是否可选。

  • exclusions:用于排除传递性依赖。

 

依赖范围

Maven 在编译、测试、运行项目时,使用的 classpath 是不同的。Maven 在编译项目主代码的时候使用一套 classpath,执行测试的时候会使用另一套 classpath,最后在实际运行项目时,又会使用一套 classpath。依赖范围就是用来控制依赖与这三种 classpath(编译 classpath、测试 classpath、运行 classpath)的关系。Maven 有以下几种依赖范围:

  • compile:编译依赖范围。如果没有指定买就会默认使用该依赖范围。使用此依赖范围的 Maven 依赖,对于编译、测试、运行三种 classpath 都有效。

  • test:测试依赖范围。使用此依赖范围的 Maven 依赖,只对于测试 classpath 有效,在编译主代码或者运行项目时将无法使用此依赖。典型的例子是 JUnit,它只有在编译测试代码及运行测试代码的时候才需要。

  • provided:已提供依赖范围。使用此依赖范围的 Maven 依赖,对于编译和测试 classpath 有效,但在运行时无效。典型的例子是 servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要 Maven 重复引入。

  • runtime:运行时依赖范围。使用此依赖范围的 Maven 依赖,对于测试和运行 classpath 有效,但在编译主代码的时候无效。典型的例子是 JDBC 驱动实现,项目主代码但编译只需要 JDK 提供的 JDBC 接口,只有在执行测试或者运行项目的时候才需要 JDBC 驱动的实现类。

  • system:系统范围依赖。该依赖与三种 classpath 的关系,和 provided 依赖范围完全一致。但是,使用 system 范围的依赖时必须通过 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>

 

传递性依赖

假设 Maven 项目引入了构件 A,而构件 A 又依赖于构件 B,那么 Maven 项目会自动引入构件 B,这就是传递性依赖。例如,spring-core 构件依赖于 commons-logging 构件,如果项目引入了 spring-core 依赖,Maven会自动将 commons-logging 引入,开发者无需再手动去加入 commons-logging 依赖。

 

依赖调解

Maven 的传递性依赖机制,不仅简化了依赖声明,而且使用者只需关心项目的直接依赖是什么。但有时候,当传递性依赖造成问题时,我们就需要清楚该传递性依赖是从哪条依赖路径引入的。

例如,项目 A 有这样的依赖关系:A->B->C->X(1.0)、A->D->X(2.0),X 是 A 的传递性依赖,但是两条依赖路径上的 X 版本不一样,那么哪个版本的 X 会被 Maven 解析使用呢?Maven 依赖调解的第一原则是:路径最近者优先。在这个例子中,X(1.0) 的路径长度为 3,而 X(2.0) 的长度为 2,因此 X(2.0) 会被解析使用。

如果有依赖路径相等呢?比如有这样的依赖关系:A->B->Y(1.0)、A->C->Y(2.0),Y(1.0) 和 Y(2.0) 的依赖路径长度一样,那么哪一个会被解析使用呢?Maven 依赖调解的第二原则是:最先声明者优先。在依赖路径长度相等的情况下,在 POM 中依赖声明的顺序决定了谁会被解析使用。在这个例子中,如果 B 的依赖声明在 C 之前,那么 Y(1.0) 就会被解析使用。

 

排除依赖

传递性依赖会给项目隐式地引入很多依赖,极大地简化了项目依赖的管理,当依赖调解选择的版本不是我们所期待的,比如引入的是一个比较旧的版本,而项目的代码使用了新的特性,需要引入的是较新的版本。这时,可以使用 exclusions 元素声明排除传递性依赖,然后再显示地声明所需版本的依赖。exclusions 元素可以包含一个或多个 exclusion 子元素,因此可以排除一个或多个传递性依赖。

 

 

 

 

 

Maven 仓库

Maven 的坐标和依赖是任何一个构件在 Maven 世界中的逻辑表示,而构件的物理表示是文件,Maven 通过仓库来统一管理这些文件。

任何一个构件都有其唯一的坐标,根据这个坐标可以定义其在仓库中的唯一存储路径,该路径与坐标大致对应关系为 groupId/artifactId/version/artifact-version.packaging。例如,org.jsoup:jsoup:1.8.3 这一依赖,其对应的仓库路径为 org/jsoup/jsoup/1.8.3/jsoup-1.8.3.jar。

 

 

仓库的分类

对于 Maven 来说,仓库只分为两类:本地仓库和远程仓库。当 Maven 根据坐标寻找构件的时候,它首先会查看本地仓库,如果本地仓库存在此构件,则直接使用;如果本地仓库不存在此构件,Maven 就会去远程仓库查找,发现需要的构件之后,下载到本地仓库再使用。如果本地仓库和远程仓库都没有需要的构件,Maven 就会报错。

在这个最基本的分类的基础上,还有一些特殊的远程仓库。中央仓库是 Maven 核心自带的远程仓库,它包含了绝大部分开源的构件。

私服是另一种特殊的远程仓库,为了节省带宽和时间,应该在局域网内架设一个私有的仓库服务器,用其代理所有外部的远程仓库。内部的项目还能部署到私服上供其他项目使用。

除了中央仓库和私服,还有很多其他公开的远程仓库,比如阿里云的 Maven 仓库

 

本地仓库

默认情况下,不管是在 Windows、Linux 还是 MacOS 上,每个用户在自己的用户目录下都有一个路径为 .m2/repository 的仓库目录。

有时用户会想要自定义本地仓库的地址,那么可以通过编辑 settings.xml 文件,设置 localRepository 元素的值为想要的仓库地址,例如:

<settings>
  ...
  <localRepository>/Users/huey/maven/repository</localRepository>
  ...
</settings>

一个构件只有在本地仓库中,才能供其他 Maven 项目使用,那么构件如何进入到本地仓库呢?最常见到是依赖 Maven 从远程仓库下载到本地仓库中。还有一种常见到情况是,将本地项目到构件安装到 Maven 仓库中。例如,本地有两个项目 A 和 B,两者都无法从远程仓库获得,而同时 A 又依赖于 B,为了构建 A,B 就必须首先构建并安装到本地仓库中。

可以在某个项目中执行 mvn clean install 命令,使用 install 插件的 install 目标将项目构建的输出文件安装到本地仓库中。

 

远程仓库

前面已经提到,当 Maven 无法从本地仓库查找到需要到构件时,它就会从远程仓库下载构件至本地仓库。每个用户只有一个本地仓库,但可以配置多个远程仓库。

 

中央仓库

由于最原始但本地仓库是空的,Maven 必须知道至少一个可用的远程仓库,才能在执行 Maven 命令的时候下载到需要的构件。中央仓库就是这样一个默认的远程仓库,Maven 的安装文件自带了中央仓库的配置。使用解压工具或者反编译工具打开 $M2_HOME/lib/maven-model-builder-3.5.3.jar,然后访问 org/apache/maven/model/pom-4.0.0.xml,可以观察到有如下配置:

<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>

这个配置文件 pom-4.0.0.xml 是所有的 Maven 项目都会继承的超级 POM。上述配置使用 central 对中央仓库进行唯一标识,仓库的地址是 https://repo.maven.apache.org/maven2

 

私服

私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,私服代理广域网上的远程仓库,供局域网内的 Maven 用户使用。当 Maven 需要下载构件的时候,它从私服请求,如果私服上不存在该构件,则从外部的远程仓库下载,缓存在私服上之后,再为 Maven 的下载请求提供服务。此外,一些无法从外部仓库下载到的构件也能从本地上传到私服供其他 Maven 项目使用。

 

 

配置远程仓库

在很多情况下,默认的中央仓库无法满足项目的需求,可能项目需要的构件存在于其他的远程仓库中。这时,可以在 POM 中配置该仓库,如下所示:

<project>
...
  <repositories>
    <repository>
      <id>aliyun</id>
      <name>Aliyun Maven Repository</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>
...
</project>

在 repositories 元素下,使用 repository 子元素声明一个或多个远程仓库。在上述的例子中,声明了一个 id 为 aliyun,名称为 Aliyun Maven Repository 的仓库。任何一个仓库声明的 id 必须是唯一的,尤其需要注意的是,默认的中央仓库使用的 id 是 central,如果其他的仓库声明也使用这个 id,就会覆盖中央仓库的配置。url 子元素的值指向了仓库的地址,通常情况下,仓库的地址都是基于 http 协议,Maven 用户可直接在浏览器访问仓库地址浏览构件。

releases 和 snapshots 元素是用来控制 Maven 对于发布版构件和快照版构件的下载。releases 的子元素 enabled 值是 true,表示开启远程仓库的发布版本的下载支持,而 snapshots 的 enabled 值是 false,表示关闭远程仓库的快照版本的下载支持。因此,根据该配置,Maven 只会从阿里云仓库下载发布版本的构件,而不会下载快照版本的构件。

对于 releases 和 snapshots 元素,除了 enabled,它还包含两个子元素  updatePolicy 和 checksumPolicy:

<snapshots>
  <enabled>true</enabled>
  <updatePolicy>daily</updatePolicy>
  <checksumPolicy>ignore</checksumPolicy>
</snapshots>

updatePolicy 元素用于配置 Maven 从远程仓库检查更新的频率,它的取值可以是:

  • daily - 默认值,表示 Maven 每天检查一次。

  • never - 从不检查更新。

  • always - 每次构建都检查更新。

  • interval:X - 每隔 X 分钟检查一次更新。

checksumPolicy 元素用于配置 Maven 检查校验和文件的策略。当构件部署到 Maven 仓库中时,会同时部署对应的校验和文件。在下载构件时,Maven 会检查校验和文件,当校验和验证失败时,Maven 会根据策略做不同的处理:

  • warn - 默认值,在执行构建时输出警告信息。

  • fail - 遇到校验和错误就构建失败。

  • ignore - 完全忽律校验和错误。

 

远程仓库的认证

大部分远程仓库无须认证就可以访问,但有时出于安全方面的考虑,需要提供认证信息才能访问一些远程仓库。配置认证信息和配置仓库信息不同,仓库信息可以直接配置在 POM 文件中,但是认证信息必须配置在 settings.xml 文件中。

下面的代码演示了为一个 id 为 myrepo 的仓库配置认证信息:

<settings>
...
  <servers>
    <server>
      <id>myrepo</id>
      <username>test</username>
      <password>test#123</password>
    </server>
  </servers>
...
</settings>

 

部署至远程仓库

Maven 除了能对项目进行编译、测试、打包之外,还能将项目生成的构件部署到仓库中。配置示例如下:

<project>
...
  <distributionManagement>
    <repository>
      <id>releases</id>
      <name>Release Repository</name>
      <url>http://192.168.1.100/nexus/content/repositories/releases</url>
    </repository>
    <snapshotRepository>
      <id>snapshots</id>
      <name>Snapshot Repository</name>
      <url>http://192.168.1.100/nexus/content/repositories/snapshots</url>
    </snapshotRepository>
  </distributionManagement>
...
</project>

distributionManagement 元素包含了 repository 和 snapshotRepository 子元素,前者表示发布版本构件的仓库,后者表示快照版本的仓库。这两个元素都要配置 id、name、url,id 为远程仓库的唯一标识,name 是仓库的名称,url 是仓库的地址。

部署构件到远程仓库通常需要认证信息,配置认证的方式已在上一节介绍。

配置正确后,在命令行执行 mvn clean deploy,Maven 就会将项目构建输出的构件部署到配置对应的远程仓库。如果项目当前的版本是快照版本,则部署到快照版本仓库地址,否则就部署到发布版本仓库的地址。

 

镜像

如果仓库 X 可以提供仓库 Y 存储的所有内容,那么就可以认为 X 是 Y 的一个镜像。也就是说,任何一个可以从仓库 Y 获得的构件,都能够从它的镜像中获取。例如,http://maven.aliyun.com/nexus/content/groups/public/ 可以认为是中央仓库 https://repo.maven.apache.org/maven2/ 的一个镜像,由于地理位置的因素,该镜像往往能够提供比中央仓库更快的服务。因此,可以配置 Maven 使用该镜像来替代中央仓库:

<settings>
...
  <mirrors>
    <mirror>
      <id>aliyun</id>
      <name>Nexus Public Mirror</name>
      <mirrorOf>central</mirrorOf>
      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
    </mirror>
  </mirrors>
...
</settings>

上述的例子中,mirrorOf 的值是 central,表示该配置为中央仓库的镜像,任何对于中央仓库的请求都会转至该镜像。另外三个子元素 id、name、url 与其他仓库配置一样,表示仓库的唯一标识、名称和地址。类似地,如果镜像需要认证,也是基于该 id 配置仓库认证。

 

 

Maven 的生命周期和插件

Maven 的生命周期是用于对构建过程进行抽象和统一,生命周期包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。Maven 的生命周期是抽象的,即生命周期本身不做任何实际的工作,在 Maven 的设计中,实际的任务是由插件完成的。

 

 

三套生命周期

Maven 拥有三套相互独立的生命周期,它们分别为 clean、default 和 site。clean 生命周期的目的是清理项目,default 生命周期的目的是构建项目,而 site 生命周期的目的是建立项目站点。

每个生命周期包含一些阶段,这些阶段是有顺序的,并且后面的阶段依赖于前面的阶段。以 clean 生命周期为例,它包含的阶段有 pre-clean、clean 和 post-clean。当用户调用 pre-clean 时,只有 pre-clean 阶段得以执行;当用户调用 clean 时,pre-clean 和 clean 阶段会得以顺序执行;当用户调用 post-clean 时,pre-clean、clean 和 post-clean 阶段会得以顺序执行。

较之于生命周期阶段的前后依赖关系,三套生命周期本身是相互独立的。用户可以仅仅调用 clean 生命周期的某个阶段,或者仅仅调用 default 生命周期的某个阶段,而不会对其他生命周期产生任何影响。

 

clean 生命周期

clean 生命周期的目的是清理项目,它包含三个阶段:

  • pre-clean:执行一些清理前需要完成的工作。

  • clean:清理上一次构建生成的文件。

  • post-clean:执行一些清理后需要完成的工作。

 

default 生命周期

default 生命周期定义了构建时所需要执行的所有步骤,它是所有生命周期中最核心的部分,它包含了如下阶段:

  • validate

  • initialize

  • generate-sources

  • process-sources:处理项目主资源文件。一般来说,是对 src/main/resources 目录的内容进行变量替换等工作后,复制到项目输出的主 classpath 目录中。

  • generate-resources

  • process-resources

  • compile:编译项目的主代码。一般来说,是编译 src/main/java 目录下的 java 文件至项目输出的主 classpath 目录中。

  • process-classes

  • generate-test-sources

  • process-test-sources:处理项目测试资源文件。一般来说,是对 src/test/resources 目录的内容进行变量替换等工作后,复制到项目输出的测试 classpath 目录中。

  • 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 本地仓库。

  • deploy:将最终的包部署到远程仓库。

 

site 生命周期

site 生命周期的目的是建立和发布项目的站点,Maven 能够基于 POM 所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息。该生命周期包含如下阶段:

  • pre-site:执行一些在生成项目站点之前需要完成的工作。

  • site:生成项目站点文档。

  • post-site:执行一些在生成项目站点之后需要完成的工作。

  • site-deploy:将生成的项目站点发布到服务器上。

命令行与生命周期

从命令行执行 Maven 任务的最主要方式就是调用 Maven 的生命周期阶段。需要注意的是,各个生命周期是相互独立的,而一个生命周期的阶段是有前后依赖关系的。下面以一些常见的 Maven 命令为例,解释其执行的生命周期阶段。

  • mvn clean - 该命令调用 clean 生命周期的 clean 阶段。实际执行的阶段为 clean 生命周期的 pre-clean 和 clean 阶段。

  • mvn test - 该命令调用 default 生命周期的 test 阶段。实际执行的阶段为 default 生命周期的 validate、initialize 等直到 test 的所有阶段。这也解释了为什么在执行测试的时候,项目的代码能够自动得以编译。

  • mvn clean install - 该命令调用 clean 生命周期的 clean 阶段和 default 生命周期的 install 阶段。实际执行的阶段为 clean 生命周期的 pre-clean 和 clean 阶段,以及 default 生命周期的从 validate 至 install 的所有阶段。该命令结合了两个生命周期,在执行真正的项目构建之前清理项目是一个很好的实践。

  • mvn clean deploy site-deploy - 该命令调用 clean 生命周期的 clean 阶段、default 生命周期的 deploy 阶段和 site 生命周期的 site-deploy 阶段。实际执行的阶段为 clean 生命周期的 pre-clean、clean 阶段,default 生命周期的所有阶段,以及 site 生命周期的所有阶段。该命令结合了三个生命周期。

 

 

插件目标

Maven 的插件是以独立的构件形式存在的,Maven 会在需要的时候下载并使用插件。对于插件本身,为了能够复用代码,它往往能够完成多个任务。例如 maven-dependency-plugin,它能够基于项目依赖做很多任务。它能够分析项目依赖,帮助找出潜在的无用依赖;它能够列出项目的依赖树,帮助分析依赖来源;它能够列出项目所有已解析的依赖等等。为每个这样的功能编写一个独立的插件显然是不可取的,因为这些任务背后有很多可以复用的代码,因此,这些功能聚集在一个插件里,每个功能就是一个插件目标。

maven-dependency-plugin 有十多个目标,每个目标对应了一个功能,上述提到的几个功能分别对应的插件目标为 dependency:analyze、dependency:tree 和 dependency:list。这是一种通用的写法,冒号前面是插件前缀,冒号后面是该插件的目标。类似地,还可以写出 compiler:compile(这是 maven-compiler-plugin 的 compile 目标)和 surefire:test(这是 maven-surefire-plugin 的 test 目标)。

 

 

插件绑定

Maven 的生命周期与插件相互绑定,用以完成实际的构建任务。具体而言,是生命周期的阶段与插件的目标相互绑定,以完成某个具体的构建任务。例如项目编译这一任务,它对应了 default 生命周期的 compile 这一阶段,而 maven-compiler-plugin 这一插件的 compile 目标能够完成该任务,因此,将它们绑定,就能实现项目编译的目的。

 

内置绑定

为了能让用户几乎不用任何配置就能构建 Maven 项目,Maven 核心为一些主要的生命周期阶段绑定了很多插件的目标,当用户通过命令行调用生命周期阶段的时候,对应的插件目标就会执行相应的任务。

clean 生命周期阶段与插件目标的绑定关系如下表所示:

生命周期阶段

插件目标

pre-clean

 

clean

maven-clean-plugin:clean

post-clean

 

site 生命周期阶段与插件目标的绑定关系如下表所示:

生命周期阶段

插件目标

pre-site

 

site

maven-site-plugin:site

post-site

 

site-deploy

maven-site-plugin:deploy

相对于 clean 和 site 生命周期,default 生命周期与插件目标的绑定关系显得比较复杂一些。这是因为对于任何项目来说,比如 jar 项目和 war 项目,它们的项目清理和站点生成任务是一样的,不过构建过程则有区别。例如 jar 项目需要打成 JAR 包,而 war 项目需要打成 WAR 包。

由于项目的打包类型会影响构建的具体过程,因此,default 生命周期的阶段与插件目标的绑定关系由项目的打包类型所决定。最常见的打包类型是 jar,它也是默认的打包类型,其 default 生命周期阶段与插件目标的绑定关系如下表所示:

生命周期阶段

插件目标

process-resources

maven-resources-plugin:resources

compile

maven-compiler-plugin:compile

process-test-resources

maven-resources-plugin:testResources

test-compile

maven-compiler-plugin:testCompile

test

maven-surefire-plugin:test

package

maven-jar-plugin:jar

install

maven-install-plugin:install

deploy

maven-deploy-plugin:deploy

注意,上表只列出了拥有插件绑定关系的阶段,default 生命周期还有很多其他阶段,默认它们没有绑定任何插件,因此也没有任何实际行为。

除了默认的打包类型 jar 之外,常见的打包类型还有 war、pom 等,它们的 default 生命周期与插件目标的绑定关系可参考 Maven 的官方文档

 

自定义绑定

除了内置绑定,用户还可以自己选择将某个插件目标绑定到生命周期到某个阶段上。一个常见的例子是创建项目的源码的 jar 包,内置的插件绑定关系并没有涉及这一任务,因此需要用户自行配置。maven-source-plugin 可以完成该任务,它的 jar-no-fork 目标能够将项目的主代码打包成 jar 文件,可以将其绑定到 default 生命周期的 verify 阶段上,在执行完集成测试后和安装构件之前创建源码的 jar 包。配置如下所示:

<project>
...
  <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>
            <phase>verify</phase>
            <goals>
              <goal>jar-no-fork</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
...
</project>

在 POM 的 build 元素下的 plugins 子元素声明一个或多个插件的使用,该例子使用的是 maven-source-plugin,其 groupId、artifactId、version 定义了插件的基本坐标,executions 下每个 execution 子元素可以用来配置执行一个任务。该例中配置了一个 id 为 attach-sources 的任务,通过 phrase 配置,将其绑定到 verify 声明周期阶段上,再通过 goal是 配置指定要执行的插件目标。至此,自定义插件绑定完成,运行 mvn verify 命令,可以观察到如下输出:

[INFO] --- maven-source-plugin:2.1.1:jar-no-fork (attach-sources) @ hello-world ---
[INFO] Building jar: /Users/huey/eclipse-workspace/hello-world/target/hello-world-0.0.1-SNAPSHOT-sources.jar

可以看到,当执行 verify 生命周期阶段当时候,maven-source-plugin:jar-no-fork 会得以执行,它会创建一个以 -sources.jar 结尾当源码文件包。

有时候,即使不通过 phase 元素配置生命周期阶段,插件目标也能够绑定到生命周期中去。例如,可以试着删除上述配置中到 phase 一行,再次执行 mvn verify,仍然可以看到 maven-source-plugin:jar-no-fork 得以执行。这是因为有很多插件到目标在编写时已经定义了默认绑定阶段。可以使用 maven-help-plugin 查看插件详细信息,了解插件目标到默认绑定阶段:

$ mvn help:describe -Dplugin=org.apache.maven.plugins:maven-source-plugin:2.1.1 -Ddetail

该命令输出对应插件到详细信息,在输出信息中,可以看到关于目标 jar-no-fork 的如下信息:

...
source:test-jar-no-fork
  Description: This goal bundles all the test sources into a jar archive.
    This goal functions the same as the test-jar goal but does not fork the
    build, and is suitable for attaching to the build lifecycle.
  Implementation: org.apache.maven.plugin.source.TestSourceJarNoForkMojo
  Language: java
  Bound to phase: package
...

Bound to phase 一项表示该目标默认绑定的生命周期阶段,这里是 package。

 

 

插件配置

完成了插件和生命周期的绑定之后,用户还可以配置插件目标的参数,进一步调整插件目标所完成的任务,以满足项目的需求。几乎所有 Maven 插件的目标都有一些可配置的参数,用户可以通过命令行和 POM 配置等方式来配置这些参数。

 

命令行插件配置

很多插件目标的参数都支持从命令行配置,用户可以在 Maven 命令中使用 -D 参数来配置插件目标的参数。例如,maven-surefire-plugin 提供了一个 maven.test.skip 参数,当其值为 true 时,就会跳过执行测试。所以,在运行命令的时候,如果加上如下 -D 参数就能跳过测试:

$ mvn install -Dmaven.test.skip=true

 

POM 中插件全局配置

并不是所有的插件参数都适合从命令行配置,有些参数都值从项目创建到项目发布都不会改变或很少改变,这种情况,在 POM 文件中一次性配置就比重复地在命令行输入要方便。

用户可以在声明插件的时候,对插件进行一个全局对配置,即这些配置会作用于所有基于该插件目标的任务。例如,下面的配置为 maven-compile-plugin 指定编译时的 jdk 版本。

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compile-plugin</artifactId>
      <version>2.1</version>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
      </configuration>
    </plugin>
  </plugins>
</build>

这样,不管绑定到 compile 阶段的 maven-compile-plugin:compile 任务,还是绑定到 test-compiler 阶段的 maven-compiler-plugin:testComplile 任务,就都会基于 jdk 1.8 版本进行编译。

 

POM 中插件任务配置

除了为插件配置全局的参数,用户还可以为某个插件任务配置特定的参数。以 maven-antrun-plugin 为例,它有一个目标 run,可以用来在 Maven 中调用 Ant 任务。用户可以将 maven-antrun-plugin:run 绑定到多个生命周期阶段上,再加以不同的配置,就可以让 Maven 在不同的生命周期阶段执行不同的任务,如下所示:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-antrun-plugin</artifactId>
      <version>2.1</version>
      <executions>
        <execution>
          <id>ant-validate</id>
          <phase>validate</phase>
          <goals>
            <goal>run</goal>
          </goals>
          <configuration>
            <tasks>
              <echo>I'm bound to validate phase.</echo>
            </tasks>
          </configuration>
        </execution>
        <execution>
          <id>ant-verify</id>
          <phase>verify</phase>
          <goals>
            <goal>run</goal>
          </goals>
          <configuration>
            <tasks>
              <echo>I'm bound to verify phase.</echo>
            </tasks>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

 

 

插件仓库

与依赖构件一样,插件构件同样是基于坐标存储在 Maven 仓库中的。在需要的时候,Maven 会从本地仓库寻找插件,如果不存在,则从远程插件仓库中查找。找到插件之后,再下载到本地仓库使用。

但是,Maven 会区别对待依赖到远程仓库与插件的远程仓库。不同于 repositories 及其 repository 子元素,插件的远程仓库使用 pluginRepositories 和 pluginRepository 配置。例如,Maven 内置了如下的插件远程仓库配置:

<pluginRepositories>
  <pluginRepository>
    <id>central</id>
    <name>Central Repository</name>
    <url>https://repo.maven.apache.org/maven2</url>
    <layout>default</layout>
    <snapshots>
      <enabled>false</enabled>
    </snapshots>
    <releases>
      <updatePolicy>never</updatePolicy>
    </releases>
  </pluginRepository>
</pluginRepositories>

除了 pluginRepositories 和 pluginRepository 标签不同之外,其余所有子元素表达的含义,与依赖远程仓库的配置完全一样。而且,默认插件仓库的地址就是中央仓库。

一般来说,中央仓库所包含的插件完全能够满足我们的需要,因此也不需要再配置其他的插件仓库。只有在很少的情况下,项目使用的插件无法在中央仓库找到,或者自己编写了插件,这时可参考上述配置,在 POM 或 settings.xml  中加入其他的插件仓库配置。

 

从命令行调用插件

我们知道,可以通过 mvn 命令激活生命周期阶段,从而执行那些绑定在生命周期阶段上的插件目标。但 Maven 还支持直接从命令行调用插件目标。Maven 支持这种方式是因为有些任务不适合绑定在生命周期上,例如 maven-dependency-plugin:tree,我们不需要在构建项目但时候去显示依赖树。因此,这类插件目标应该通过如下方式使用:

$ mvn dependency:tree

这条命令等价于下面这条命令:

$ mvn org.apache.maven.plugins:maven-dependency-plugin:2.8:tree

下面这条命令显示地声明了插件的 groupId、artifactId、version 和 goal,但前面但命令更简洁,更容易记忆和使用。为什么 org.apache.maven.plugins:maven-dependency-plugin:2.8 能精简成 dependency 呢?

  1. 插件默认的 groupId:如果插件是 Maven 的官方插件(即 groupId 为 org.apache.maven.plugins),则可以省略。

  1. Maven 引入了插件前缀的概念,在插件仓库中的 org/apache/maven/plugins/maven-metadata.xml,为官方的插件指定了前缀。maven-dependency-plugin 的前缀是 dependency,配置片段如下:

    <plugin>
      <name>Apache Maven Dependency Plugin</name>
      <prefix>dependency</prefix>
      <artifactId>maven-dependency-plugin</artifactId>
    </plugin>
  1. 如果没有指定插件版本,Maven 会自动解析插件版本。首先,Maven 在超级 POM 中为所有的核心插件设定了版本,这些插件包括 maven-clean-plugin、maven-compiler-plugin、maven-dependency-plugin 等。如果插件不属于核心版本范畴,则会根据仓库元数据计算插件版本的 release 值,作为版本。以 maven-dependency-plugin 为例,它的元数据文件是 org/apache/maven/plugins/maven-dependency-plugin/maven-metadata.xml。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值