Maven-精要-全-
Maven 精要(全)
原文:
zh.annas-archive.org/md5/62cecc480e1bf2b3605080f62bfc2a69译者:飞龙
前言
Maven 是开发者使用的首选构建工具,它已经存在十多年了。Maven 因其基于约定优于配置概念的极其可扩展的架构而脱颖而出,这实际上使 Maven 成为管理和构建 Java 项目的默认工具。它被许多开源 Java 项目广泛使用,包括 Apache 软件基金会、SourceForge、Google Code 等。
本书提供了一步一步的指南,展示了读者如何以最佳方式使用 Apache Maven 来满足企业构建需求。遵循本书,读者将能够深入理解以下关键领域:
-
如何开始使用 Apache Maven,应用 Maven 最佳实践以设计构建系统以提高开发者的生产力
-
如何通过使用适当的 Maven 插件、生命周期和原型来定制构建过程,以完全满足你企业的需求
-
如何更有信心地解决构建问题
-
如何设计构建方式,以避免因适当的依赖管理而产生任何维护噩梦
-
如何优化 Maven 配置设置
-
如何使用 Maven 组装构建自己的分发存档
本书涵盖的内容
第一章,Apache Maven 快速入门,专注于围绕 Maven 构建基本基础以开始使用。它首先解释了在 Ubuntu、Mac OS X 和 Microsoft Windows 操作系统上安装和配置 Maven 的基本步骤。本章的后半部分涵盖了某些常见的有用 Maven 技巧和窍门。
第二章,理解项目对象模型(POM),专注于 Maven 项目对象模型(POM)以及如何遵守行业广泛接受的最佳实践以避免维护噩梦。POM 文件的关键元素、POM 层次结构和继承、管理依赖项以及相关主题在此处得到涵盖。
第三章,Maven 原型,专注于 Maven 原型如何提供一种减少构建 Maven 项目重复工作的方法。有数千个原型可供公开使用,帮助你构建不同类型的项目。本章涵盖了一组常用的原型。
第四章,Maven 插件,涵盖了最常用的 Maven 插件,并解释了插件是如何被发现和执行的。Maven 只提供构建框架,而 Maven 插件执行实际的任务。Maven 有一套庞大、丰富的插件,你几乎不需要编写自己的自定义插件。
第五章,构建生命周期,解释了三个标准生命周期是如何工作的,以及我们如何自定义它们。在章节的后面部分,我们讨论了如何开发我们自己的生命周期扩展。
第六章, Maven 组件,详细介绍了如何使用 Maven 组件插件的实际案例,并最终以一个端到端的 Maven 项目示例结束。
第七章,最佳实践,探讨了在大型 Maven 开发项目中应遵循的一些最佳实践。始终建议遵循最佳实践,因为它将极大地提高开发者的生产力,并减少任何维护噩梦。
您需要为此书准备的
要跟随本书中的示例,您需要以下软件:
-
Apache Maven 3.3.x,您可以在
maven.apache.org/download.cgi找到。 -
Java 1.7+ SDK,您可以在
www.oracle.com/technetwork/java/javase/downloads/index.html找到。 -
操作系统:Windows、Linux 或 Mac OS X。
本书面向的对象
本书非常适合已经熟悉构建自动化但想学习如何使用 Maven 并将其概念应用于构建自动化中最困难场景的资深开发者。
约定
在本书中,您将找到许多不同的文本样式,以区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称如下所示:"当您输入mvn clean install时,Maven 将执行默认生命周期中的所有阶段直到install阶段(包括install阶段)。"
代码块如下设置:
<project>
[...]
<build>
[...]
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
[...]
</build>
[...]
</project>
当我们希望引起您对代码块中特定部分的注意时,相关的行或项目将以粗体显示:
<project>
[...]
<build>
[...]
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
[...]
</build>
[...]
</project>
任何命令行输入或输出都应如下编写:
$ mvn install:install
新术语和重要词汇以粗体显示。
注意
警告或重要注意事项以如下框中的形式出现。
提示
技巧和窍门如下所示。
读者反馈
我们始终欢迎读者的反馈。告诉我们您对本书的看法——您喜欢或不喜欢的地方。读者反馈对我们来说很重要,因为它帮助我们开发出您真正能从中受益的标题。
要向我们发送一般反馈,请简单地发送电子邮件至 <feedback@packtpub.com>,并在邮件主题中提及本书的标题。
如果您在某个主题上具有专业知识,并且您对撰写或为本书做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
现在您是 Packt 图书的骄傲拥有者,我们有多种方式帮助您从您的购买中获得最大收益。
下载示例代码
您可以从您在www.packtpub.com的账户下载示例代码文件,适用于您购买的所有 Packt 出版图书。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
勘误
尽管我们已经尽最大努力确保内容的准确性,错误仍然可能发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以避免其他读者感到沮丧,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。
要查看之前提交的勘误,请访问www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在勘误部分下。
侵权
在互联网上,版权材料侵权是一个持续存在的问题,涉及所有媒体。在 Packt,我们非常重视保护我们的版权和许可证。如果您在互联网上发现任何形式的非法复制我们的作品,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过发送链接到疑似盗版材料的方式与我们联系 <copyright@packtpub.com>。
我们感谢您在保护我们的作者和我们为您提供有价值内容的能力方面的帮助。
问题
如果您对本书的任何方面有问题,您可以通过发送电子邮件到 <questions@packtpub.com> 来联系我们,我们将尽力解决问题。
第一章:Apache Maven 快速入门
Apache Maven 作为构建工具非常受欢迎。然而,实际上,它不仅仅是一个构建工具。它提供了一个全面的构建管理平台。在 Maven 之前,开发者必须花费大量时间来构建构建系统。没有统一的接口。它因项目而异,每次开发者从一个项目转移到另一个项目时,都需要一个学习曲线。Maven 通过引入一个统一的接口来填补这一空白。它仅仅结束了构建工程师的时代。
在本章中,我们将讨论以下主题:
-
在 Ubuntu、Mac OS X 和 Microsoft Windows 上安装和配置 Maven
-
IDE 集成
-
使用 Maven 的高效技巧和窍门
安装 Apache Maven
在任何平台上安装 Maven 不仅仅是一个简单的任务。在撰写本书时,最新版本是 3.3.3,可在 maven.apache.org/download.cgi 下载。此版本需要 JDK 1.7.0 或更高版本。
小贴士
如果你计划从 3.0.、3.1. 或 3.2.* 版本升级,你应该注意版本 3.3.3 的 Java 要求。在 Maven 3.3.x 之前,唯一的要求是 JDK 1.5.0 或 JDK 1.6.0(对于 3.2.*)。
Apache Maven 是一个非常轻量级的发行版。它对内存、磁盘空间或 CPU 没有任何硬性要求。Maven 本身是基于 Java 构建的,并且可以在任何运行 Java 虚拟机(JVM)的操作系统上运行。
在 Ubuntu 上安装 Apache Maven
在 Ubuntu 上安装 Maven 只需一条命令。按照以下步骤进行操作:
-
在命令提示符中运行以下
apt-get命令。你需要有sudo权限来执行此操作:$ sudo apt-get install maven -
这需要几分钟才能完成。安装完成后,你可以运行以下命令来验证安装:
$ mvn -version -
如果 Apache Maven 安装成功,你应该会得到以下类似的输出:
$ mvn -version Apache Maven 3.3.3 Maven home: /usr/share/maven Java version: 1.7.0_60, vendor: Oracle Corporation Java home: /usr/lib/jvm/java-7-oracle/jre Default locale: en_US, platform encoding: UTF-8 OS name: "linux", version: "3.13.0-24-generic", arch: "amd64", family: "unix" -
Maven 安装在
/usr/share/maven目录下。要检查 Maven 安装目录背后的目录结构,请使用以下命令:$ ls /usr/share/maven bin boot conf lib man -
Maven 配置文件位于
/etc/maven,可以使用以下命令列出:$ ls /etc/maven m2.conf settings.xml
如果你不想使用 apt-get 命令,在基于 Unix 的任何操作系统下安装 Maven 有另一种方法。我们将在下一节讨论这个问题。由于 Mac OS X 是基于 Unix 内核构建的内核,因此,在 Mac OS X 上安装 Maven 与在基于 Unix 的任何操作系统上安装它相同。
在 Mac OS X 上安装 Apache Maven
在 OS X Mavericks 之前的许多 OS X 发行版中预装了 Apache Maven。为了验证你的系统中是否已安装 Maven,尝试以下命令。如果没有版本输出,则意味着你没有安装它:
$ mvn –version
以下步骤将指导你在 Max OS X Yosemite 上进行 Maven 安装过程:
-
首先,我们需要下载 Maven 的最新版本。在本书中,我们将使用 Maven 3.3.3,这是撰写本书时的最新版本。Maven 3.3.3 ZIP 分发版可以从
maven.apache.org/download.cgi下载。 -
将下载的 ZIP 文件解压到
/usr/share/java目录中。你需要有sudo权限来执行此操作:$ sudo unzip apache-maven-3.3.3-bin.zip -d /usr/share/java/ -
如果你的系统中已经安装了 Maven,请使用以下命令进行解除链接。
/usr/share/maven仅是 Maven 安装目录的符号链接:$ sudo unlink /usr/share/maven -
使用以下命令创建指向你刚刚解压的最新 Maven 分发的符号链接。你需要有
sudo权限来执行此操作:$ sudo ln -s /usr/share/java/apache-maven-3.3.3 /usr/share/maven -
使用以下命令更新
PATH环境变量的值:$ export PATH=$PATH:/usr/share/maven/bin -
使用以下命令更新(或设置)
M2_HOME环境变量的值:$ export M2_HOME=/usr/share/maven -
使用以下命令验证 Maven 的安装:
$ mvn -version Apache Maven 3.3.3 (7994120775791599e205a5524ec3e0dfe41d4a06; 2015-04-22T04:57:37-07:00) Maven home: /usr/share/maven Java version: 1.7.0_75, vendor: Oracle Corporation Java home: /Library/Java/JavaVirtualMachines/jdk1.7.0_75.jdk/Contents/Hom e/jre Default locale: en_US, platform encoding: UTF-8 OS name: "mac os x", version: "10.10.2", arch: "x86_64", family: "mac" -
如果在运行前面的命令时出现以下错误,这意味着你的系统中正在运行另一个版本的 Maven,并且
PATH系统变量包含了其bin目录的路径。如果是这种情况,你需要通过删除旧 Maven 安装路径来清理PATH系统变量的值:-Dmaven.multiModuleProjectDirectory system property is not set. Check $M2_HOME environment variable and mvn script match.
注意
Maven 也可以使用 Homebrew 在 Mac OS X 上安装。此视频详细解释了安装过程——
www.youtube.com/watch?v=xTzLGcqUf8k
在 Microsoft Windows 上安装 Apache Maven
首先,我们需要下载 Maven 的最新版本。Apache Maven 3.3.3 ZIP 分发版可以从 maven.apache.org/download.cgi 下载。然后,我们需要执行以下步骤:
-
将下载的 ZIP 文件解压到
C:\Program Files\ASF目录中。 -
设置
M2_HOME环境变量并将其指向C:\Program Files\ASF\apache-maven-3.3.3。 -
在命令提示符下使用以下命令验证 Maven 的安装:
mvn –version
注意
要了解更多关于如何在 Microsoft Windows 上设置环境变量的信息,请参阅 www.computerhope.com/issues/ch000549.htm。
配置堆大小
一旦你在系统中安装了 Maven,下一步就是对其进行微调以实现最佳性能。默认情况下,最大堆分配为 512 MB,从 256 MB (-Xms256m 到 -Xmx512m) 开始。默认限制对于构建大型、复杂的 Java 项目来说不够好,建议你至少有 1024 MB 的最大堆。
如果你在 Maven 构建过程中任何时刻遇到 java.lang.OutOfMemoryError,那么这通常是由于内存不足造成的。你可以使用 MAVEN_OPTS 环境变量在全局级别设置 Maven 的最大允许堆大小。以下命令将在任何基于 Unix 的操作系统(包括 Linux 和 Mac OS X)中设置堆大小。确保设置的堆大小不超过运行 Maven 的机器的系统内存:
$ export MAVEN_OPTS="-Xmx1024m -XX:MaxPermSize=128m"
如果你使用的是 Microsoft Windows,请使用以下命令:
$ set MAVEN_OPTS=-Xmx1024m -XX:MaxPermSize=128m
在这里,-Xmx 用于指定最大堆大小,而 -XX:MaxPermSize 用于指定最大 永久代(PermGen)大小。
注意
Maven 在 JVM 上作为 Java 进程运行。随着构建的进行,它不断创建 Java 对象。这些对象存储在分配给 Maven 的内存中。存储 Java 对象的内存区域被称为堆。堆在 JVM 启动时创建,随着创建的对象越来越多,它将增加到定义的最大限制。-Xms JVM 标志用于指示 JVM 在创建堆时应设置的最低值。-Xmx JVM 标志设置最大堆大小。
PermGen 是 JVM 管理的内存区域,用于存储 Java 类的内部表示。可以通过 -XX:MaxPermSize JVM 标志设置 PermGen 的最大大小。
当 Java 虚拟机无法为 Maven 分配足够的内存时,可能会导致 OutOfMemoryError。要了解更多关于 Maven OutOfMemoryError 的信息,请参阅 cwiki.apache.org/confluence/display/MAVEN/OutOfMemoryError。
嗨,Maven!
开始使用 Maven 项目的最简单方法是使用 archetype 插件的 generate 目标来生成一个简单的 Maven 项目。Maven 架构在 第三章、Maven 架构 中详细讨论,插件在 第四章、Maven 插件 中介绍。
让我们从简单的例子开始:
$ mvn archetype:generate
-DgroupId=com.packt.samples
-DartifactId=com.packt.samples.archetype
-Dversion=1.0.0
-DinteractiveMode=false
此命令将调用 Maven archetype 插件的 generate 目标来创建一个简单的 Java 项目。你会看到以下项目结构被创建,其中包含一个示例 POM 文件。根目录或基本目录的名称是从 artifactId 参数的值派生出来的:
com.packt.samples.archetype
|-pom.xml
|-src
|-main/java/com/packt/samples/App.java
|-test/java/com/packt/samples/AppTest.java
示例 POM 文件将只包含对 junit JAR 文件的依赖,作用域为 test:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.samples</groupId>
<artifactId>com.packt.samples.archetype</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<name>com.packt.samples.archetype</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
生成的 App.java 类将包含以下模板代码。包的名称是从提供的 groupId 参数派生出来的。如果你想将不同的值用作包名,那么你需要将此值作为命令本身的一部分传递,例如 -Dpackage=com.packt.samples.application:
package com.packt.samples;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello World!" );
}
}
要构建示例项目,请从存在 pom.xml 文件的 com.packt.samples.archetype 目录中运行以下命令:
$ mvn clean install
契约优于配置
约定优于配置是 Apache Maven 背后的主要设计哲学之一。让我们通过几个例子来了解一下。
可以使用以下配置文件(pom.xml)创建一个完整的 Maven 项目:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>sample-one</artifactId>
<version>1.0.0</version>
</project>
注意
Maven 的 POM 文件以 <project> 元素开始。始终使用模式定义 <project> 元素。一些工具没有它无法验证文件:
<project xmlns=http://maven.apache.org/POM/4.0.0
xmlns:xsi=………
xsi:schemaLocation="…">
pom.xml 文件是任何 Maven 项目的核心,并在 第二章") 理解项目对象模型 (POM) 中详细讨论。复制前面的配置元素,并从中创建一个 pom.xml 文件。然后,将其放置在名为 chapter-01 的目录中,然后在其中创建以下子目录:
-
chapter-01/src/main/java -
chapter-01/src/test/java
现在,你可以在 chapter-01/src/main/java 下放置你的 Java 代码,并在 chapter-01/src/test/java 下放置测试用例。使用以下命令从 pom.xml 所在的位置运行 Maven 构建:
$ mvn clean install
你在示例 pom.xml 文件中找到的这个小配置与许多约定相关联:
-
Java 源代码位于
{base-dir}/src/main/java -
测试用例位于
{base-dir}/src/test/java -
生成的构件类型是 JAR 文件
-
编译后的类文件被复制到
{base-dir}/target/classes -
最终的构件被复制到
{base-dir}/target -
repo.maven.apache.org/maven2,被用作仓库 URL。
如果有人需要覆盖 Maven 的默认、传统行为,这也是可能的。以下示例 pom.xml 文件展示了如何覆盖一些前面的默认值:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>sample-one</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<build>
<sourceDirectory>${basedir}/src/main/java</sourceDirectory>
<testSourceDirectory>${basedir}/src/test/java
</testSourceDirectory>
<outputDirectory>${basedir}/target/classes
</outputDirectory>
</build>
</project>
Maven 仓库
Maven 如何找到并加载给定 Maven 项目的依赖项背后的魔法是 Maven 仓库。在你的 Maven 项目的相应 pom.xml 文件中,在 <dependencies> 元素下,你可以定义所有构建项目所需的依赖项的引用。pom.xml 文件中定义的每个依赖项都使用 Maven 坐标唯一标识。Maven 坐标唯一标识一个项目、依赖项或 POM 中定义的插件。每个实体都由组合的组标识符、构件标识符和版本(当然,还有打包和分类器)唯一标识。Maven 坐标在 第二章") 理解项目对象模型 (POM) 中详细讨论。一旦 Maven 找出给定项目的所有必需依赖项,它将它们加载到 Maven 仓库的本地文件系统中,并将它们添加到项目的类路径中。
按照惯例,Maven 使用 repo.maven.apache.org/maven2 作为仓库。如果构建项目所需的所有工件都存在于这个仓库中,那么它们将被加载到本地文件系统或本地 Maven 仓库中,默认情况下,位于 USER_HOME/.m2/repository。您可以在 pom.xml 文件的 <repositories> 元素下在项目级别添加自定义仓库,或者在 MAVEN_HOME/conf/settings.xml 文件下在全局级别添加。
IDE 集成
大多数硬核开发者都不想离开他们的 IDE。不仅是为了编码,还包括构建、部署、测试,如果可能的话,他们愿意从 IDE 本身完成所有这些。大多数流行的 IDE 都支持 Maven 集成,并且它们已经开发了各自的插件来支持 Maven。
NetBeans 集成
NetBeans 6.7 或更高版本自带内置的 Maven 集成,而 NetBeans 7.0 则包含 Maven 3 的完整副本,并在构建时运行,就像您从命令行运行一样。对于 6.9 或更早版本,您必须下载 Maven 构建,并配置 IDE 以运行此构建。有关 Maven 和 NetBeans 集成的更多信息,请参阅 wiki.netbeans.org/MavenBestPractices。
IntelliJ IDEA 集成
IntelliJ IDEA 内置了对 Maven 的支持;因此,您不需要执行任何额外的步骤来安装它。有关 Maven 和 IntelliJ IDEA 集成的更多信息,请参阅 wiki.jetbrains.net/intellij/Creating_and_importing_Maven_projects。
Eclipse 集成
M2Eclipse 项目通过 Eclipse IDE 提供了一流的 Maven 支持。有关 Maven 和 Eclipse 集成的更多信息,请参阅 www.eclipse.org/m2e/。
注意
由 Packt Publishing 出版的书籍 Maven for Eclipse 详细讨论了 Maven 和 Eclipse 集成,请参阅 www.packtpub.com/application-development/maven-eclipse。
故障排除
如果一切正常,我们就不必担心故障排除。然而,大多数情况下并非如此。Maven 构建可能会因许多原因而失败,其中一些在您的控制范围内,也有一些不在您的控制范围内。了解适当的故障排除技巧有助于您确定确切的问题。以下部分列出了一些最常用的故障排除技巧。随着本书的进行,我们将扩展这个列表。
启用 Maven 调试级别日志
一旦启用 Maven 调试级别日志记录,它将在构建过程中打印出它所采取的所有操作。要启用调试级别日志记录,请使用以下命令:
$ mvn clean install –X
构建依赖树
如果你发现在你的 Maven 项目中任何依赖项存在问题,第一步是构建一个依赖树。这显示了每个依赖项的来源。要构建依赖树,请在你的项目 POM 文件上运行以下命令:
$ mvn dependency:tree
以下显示了针对 Apache Rampart 项目执行的前一个命令的截断输出:
[INFO] --------------------------------------------------------------
[INFO] Building Rampart - Trust 1.6.1-wso2v12
[INFO] --------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ rampart-trust ---
[INFO] org.apache.rampart:rampart-trust:jar:1.6.1-wso2v12
[INFO] +- org.apache.rampart:rampart-policy:jar:1.6.1-wso2v12:compile
[INFO] +- org.apache.axis2:axis2-kernel:jar:1.6.1-wso2v10:compile
[INFO] | +- org.apache.ws.commons.axiom:axiom-api:jar:1.2.11-wso2v4:compile (version managed from 1.2.11)
[INFO] | | \- jaxen:jaxen:jar:1.1.1:compile
[INFO] | +- org.apache.ws.commons.axiom:axiom-impl:jar:1.2.11-wso2v4:compile (version managed from 1.2.11)
[INFO] | +- org.apache.geronimo.specs:geronimo-ws-metadata_2.0_spec:jar:1.1.2:compile
[INFO] | +- org.apache.geronimo.specs:geronimo-jta_1.1_spec:jar:1.1:compile
[INFO] | +- javax.servlet:servlet-api:jar:2.3:compile
[INFO] | +- commons-httpclient:commons-httpclient:jar:3.1:compile
[INFO] | | \- commons-codec:commons-codec:jar:1.2:compile
[INFO] | +- commons-fileupload:commons-fileupload:jar:1.2:compile
查看所有环境变量和系统属性
如果你系统中有多个 JDK 安装,你可能想知道 Maven 使用的是哪个。以下命令将显示为给定 Maven 项目设置的 所有环境变量和系统属性:
$ mvn help:system
以下命令的截断输出如下:
======================Platform Properties Details====================
=====================================================================
System Properties
=====================================================================
java.runtime.name=Java(TM) SE Runtime Environment
sun.boot.library.path= /Library/Java/JavaVirtualMachines/jdk1.7.0_75.jdk/Contents/Home/jre/lib
java.vm.version= 24.75-b04
awt.nativeDoubleBuffering=true
gopherProxySet=false
mrj.build=11M4609
java.vm.vendor=Apple Inc.
java.vendor.url=http://www.apple.com/
guice.disable.misplaced.annotation.check=true
path.separator=:
java.vm.name=Java HotSpot(TM) 64-Bit Server VM
file.encoding.pkg=sun.io
sun.java.launcher=SUN_STANDARD
user.country=US
sun.os.patch.level=unknown
========================================================
Environment Variables
========================================================
JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
HOME=/Users/prabath
TERM_SESSION_ID=C2CEFB58-4705-4C67-BE1F-9E4179F96391
M2_HOME=/usr/share/maven/maven-3.3.3/
COMMAND_MODE=unix2003
Apple_PubSub_Socket_Render=/tmp/launch-w7NZbG/Render
LOGNAME=prabath
USER=prabath
查看有效的 POM 文件
当配置中没有覆盖配置参数时,Maven 使用默认值。这正是我们在 约定优于配置 部分讨论的内容。如果我们使用本章之前使用的相同样本 POM 文件,我们可以看到使用以下命令的有效 POM 文件将如何显示。这也是查看 Maven 使用默认值的最有效方式:
$ mvn help:effective-pom
注意
关于 effective-pom 命令的更多细节在第二章")中讨论,理解项目对象模型 (POM)。
查看依赖类路径
以下命令将列出构建 classpath 中的所有 JAR 文件和目录:
$ mvn dependency:build-classpath
以下显示了针对 Apache Rampart 项目执行的前一个命令的截断输出:
[INFO] --------------------------------------------------------------
[INFO] Building Rampart - Trust 1.6.1-wso2v12
[INFO] --------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.1:build-classpath (default-cli) @ rampart-trust ---
[INFO] Dependencies classpath:
/Users/prabath/.m2/repository/bouncycastle/bcprov-jdk14/140/bcprov-jdk14-140.jar:/Users/prabath/.m2/repository/commons-cli/commons-cli/1.0/commons-cli-1.0.jar:/Users/prabath/.m2/repository/commons-codec/commons-codec/1.2/commons-codec-1.2.jar:/Users/prabath/.m2/repository/commons-collections/commons-collections/3.1/commons-collections-3.1.jar
摘要
本章重点介绍了围绕 Maven 建立基本知识体系,以便将所有读者带入一个共同的基础。它从解释在 Ubuntu、Mac OS X 和 Microsoft Windows 操作系统下安装和配置 Maven 的基本步骤开始。本章的后半部分涵盖了 Maven 的一些常用技巧和窍门。随着本书的进行,本章中提到的某些概念将得到详细讨论。
在下一章中,我们将详细讨论Maven 项目对象模型(POM)。
第二章 理解项目对象模型(POM)
POM 是任何 Maven 项目的核心。本章重点介绍与 POM 相关的核心概念和最佳实践,这些概念和最佳实践与构建大型、多模块 Maven 项目有关。
随着本章的进行,以下主题将详细讨论:
-
POM 层级、超级 POM 和父 POM
-
扩展和覆盖 POM 文件
-
Maven 坐标
-
管理依赖
-
传递性依赖
-
依赖范围和可选依赖
项目对象模型(POM)
任何 Maven 项目都必须有一个 pom.xml 文件。POM 是 Maven 项目的描述符,就像您的 Java EE 网络应用程序中的 web.xml 文件,或者您的 Ant 项目中的 build.xml 文件一样。以下代码列出了 Maven pom.xml 文件中的所有关键元素。随着本书的进行,我们将讨论如何以最有效的方式使用每个元素:
<project>
<parent>...</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<packaging>...</packaging>
<name>...</name>
<description>...</description>
<url>...</url>
<inceptionYear>...</inceptionYear>
<licenses>...</licenses>
<organization>...</organization> <developers>...</developers>
<contributors>...</contributors>
<dependencies>...</dependencies>
<dependencyManagement>...</dependencyManagement>
<modules>...</modules>
<properties>...</properties>
<build>...</build>
<reporting>...</reporting>
<issueManagement>...</issueManagement> <ciManagement>...</ciManagement> <mailingLists>...</mailingLists>
<scm>...</scm>
<prerequisites>...</prerequisites>
<repositories>...</repositories> <pluginRepositories>...</pluginRepositories>
<distributionManagement>...</distributionManagement>
<profiles>...</profiles>
</project>
以下代码展示了示例 pom.xml 文件:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>jose</artifactId>
<version>1.0.0</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-scm-plugin</artifactId>
<version>1.9</version>
<configuration>
<connectionType>connection</connectionType>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>2.26</version>
</dependency>
</dependencies>
</project>
POM 层级
POM 文件之间维护着父子关系。子 POM 文件从其父 POM 继承所有配置元素。使用这个技巧,Maven 坚持其设计哲学约定优于配置。任何 Maven 项目的最小 POM 配置非常简单,如下所示:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>sample-one</artifactId>
<version>1.0.0</version>
</project>
提示
下载示例代码
您可以从您在 www.packtpub.com 的账户下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问 www.packtpub.com/support 并注册,以便将文件直接通过电子邮件发送给您。
超级 POM
任何 POM 文件都可以指向其父 POM。如果父 POM 元素缺失,则存在一个系统范围内的 POM 文件,它自动被视为父 POM。这个 POM 文件被称为超级 POM。最终,所有应用程序 POM 文件都从超级 POM 扩展而来。超级 POM 文件位于 POM 层级的顶部,并打包在 MAVEN_HOME/lib/maven-model-builder-3.3.3.jar - org/apache/maven/model/pom-4.0.0.xml 中。在 Maven 2 中,它打包在 maven-2.X.X-uber.jar 中。所有默认配置都在超级 POM 文件中定义。即使是 POM 文件的最简单形式也会继承超级 POM 中定义的所有配置。无论您需要覆盖什么配置,您都可以通过在您的应用程序 POM 文件中重新定义相同的部分来实现。以下代码行显示了与 Maven 3.3.3 一起提供的超级 POM 文件配置:
<project>
<modelVersion>4.0.0</modelVersion>
Maven 中央仓库是在 repositories 部分定义的唯一仓库。它将被所有 Maven 应用程序模块继承。Maven 使用在 repositories 部分定义的这些仓库在 Maven 构建过程中下载所有依赖项。以下代码片段显示了 pom.xml 中的配置块,用于定义仓库:
<repositories>
<repository>
<id>central</id>
<name>Central Repository</name>
<url>http://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
注意
Maven 中有两种类型的仓库:本地和远程。本地仓库存储在您的本地机器上——默认位置为USER_HOME/.m2/repository。您使用mvn install在本地构建的任何内容都将部署到本地仓库。当您从一个全新的 Maven 仓库开始时,它将是空的。您需要下载所有内容——从最简单的maven-compiler-plugin到您项目的所有依赖项。Maven 构建可以是在线或离线构建。默认情况下,它是在线构建,除非您在 Maven 构建命令中添加-o。如果是离线构建,Maven 假设所有相关工件都已在本地 Maven 仓库中准备好;如果没有,它将报错。如果是在线构建,Maven 将从远程仓库下载工件并将它们存储在本地仓库中。可以通过编辑MAVEN_HOME/conf/settings.xml来更改 Maven 本地仓库的位置,以更新localRepository元素的值:
<localRepository>/path/to/local/repo</localRepository>
插件仓库定义了查找 Maven 插件的位置。我们将在第四章Maven 插件中讨论 Maven 插件。以下代码片段显示了与插件仓库相关的配置:
<pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Central Repository</name>
<url>http://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<updatePolicy>never</updatePolicy>
</releases>
</pluginRepository>
</pluginRepositories>
build配置部分包括构建项目所需的所有信息:
<build>
<directory>${project.basedir}/target</directory>
<outputDirectory>${project.build.directory}/classes
</outputDirectory>
<finalName>${project.artifactId}-${project.version}
</finalName>
<testOutputDirectory>${project.build.directory}/test-classes
</testOutputDirectory>
<sourceDirectory>${project.basedir}/src/main/java
</sourceDirectory>
<scriptSourceDirectory>${project.basedir}/src/main/scripts
</scriptSourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/java
</testSourceDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources
</directory>
</resource>
</resources>
<testResources>
<testResource>
<directory>${project.basedir}/src/test/resources
</directory>
</testResource>
</testResources>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.3</version>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-5</version>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
</plugin>
<plugin>
<artifactId>maven-release-plugin</artifactId>
<version>2.3.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
reporting部分包括报告插件的详细信息,这些插件用于生成报告,随后在 Maven 生成的网站上显示。超级 POM 仅提供输出目录的默认值:
<reporting>
<outputDirectory>${project.build.directory}/site
</outputDirectory>
</reporting>
以下代码片段定义了默认的构建配置文件。当在应用程序级别没有定义配置文件时,默认构建配置文件将被执行。我们将在第七章最佳实践中讨论配置文件:
<profiles>
<profile>
<id>release-profile</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<inherited>true</inherited>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<inherited>true</inherited>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<inherited>true</inherited>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<updateReleaseInfo>true</updateReleaseInfo>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
以下图显示了超级 POM 文件的抽象视图,其中包含关键配置元素:

POM 扩展和覆盖
让我们看看 POM 覆盖是如何工作的。在以下示例中,我们将repositories部分扩展以添加比 Maven 超级 POM 中定义的更多仓库:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>sample-one</artifactId>
<version>1.0.0</version>
<repositories>
<repository>
<id>wso2-nexus</id>
<name>WSO2 internal Repository</name>
<url>http://maven.wso2.org/nexus/content/
groups/wso2-public/
</url>
<releases>
<enabled>true</enabled>
<updatePolicy>daily</updatePolicy>
<checksumPolicy>ignore</checksumPolicy>
</releases>
</repository>
</repositories>
</project>
从上述 POM 文件所在的目录执行以下命令:
$ mvn help:effective-pom
这将显示应用程序的有效 POM,它结合了来自超级 POM 文件的所有默认设置以及您在应用程序 POM 中定义的配置。在以下代码片段中,您可以看到超级 POM 文件中的<repositories>部分正被您的应用程序特定配置扩展。现在,<repositories>部分包含了超级 POM 中定义的中心仓库以及您应用程序特定的仓库:
<repositories>
<repository>
<releases>
<enabled>true</enabled>
<updatePolicy>daily</updatePolicy>
<checksumPolicy>ignore</checksumPolicy>
</releases>
<id>wso2-nexus</id>
<name>WSO2 internal Repository</name>
<url>
http://maven.wso2.org/nexus/content/groups/wso2-public/
</url>
</repository>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
</repositories>
如果你想要覆盖从超级 POM 文件继承的 Maven 中央仓库的任何配置元素,那么你必须在你的应用程序 POM 中定义一个具有相同仓库 id(与 Maven 中央仓库相同)的仓库,并覆盖你需要的配置元素。
Maven POM 层次结构的一个主要优势是,你可以扩展以及覆盖从顶层继承的配置。比如说,你可能需要保留超级 POM 中定义的所有插件,但只想覆盖 maven-release-plugin 的 version。以下配置显示了如何操作。默认情况下,在超级 POM 中,maven-release-plugin 的版本是 2.3.2,而在这里,我们在应用程序 POM 中将其更新为 2.5。如果你再次对更新的 POM 文件运行 mvn help:effective-pom,你会注意到插件 version 已更新,而来自超级 POM 的其余插件配置保持不变:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>sample-one</artifactId>
<version>1.0.0</version>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-release-plugin</artifactId>
<version>2.5</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
要覆盖 POM 层次结构中给定元素或工件的配置,Maven 应该能够唯一标识相应的工件。在前面的场景中,插件是通过其 artifactId 来识别的。在 第四章 Maven 插件 中,我们将进一步讨论 Maven 如何定位插件。
Maven 坐标
Maven 坐标唯一标识了一个在 POM 中定义的项目、依赖项或插件。每个实体都通过组合一个组标识符、工件标识符和版本(当然,还有打包方式和分类器)来唯一标识。组标识符是一种将不同的 Maven 工件分组的方式。例如,一个公司生产的工件集可以放在同一个组标识符下。工件标识符是识别工件的方式,这可能是一个 JAR、WAR 或任何在给定组内唯一标识的工件。version 元素允许你在同一个存储库中保持同一工件的多个版本。
注意
一个有效的 Maven POM 文件必须包含 groupId、artifactId 和 version。groupId 和 version 元素也可以从父 POM 继承。
给定 Maven 工件的三个坐标都用于定义其在 Maven 仓库中的路径。如果我们以以下示例为例,相应的 JAR 文件被安装到本地仓库的路径为 M2_REPO/repository/com/packt/sample-one/1.0.0/:
<groupId>com.packt</groupId>
<artifactId>sample-one</artifactId>
<version>1.0.0</version>
如果你仔细阅读了超级 POM 文件的内容,可能会注意到它没有之前提到的任何元素——没有 groupId、artifactId 或 version。这难道意味着超级 POM 文件不是一个有效的 POM 文件吗?超级 POM 文件类似于 Java 中的抽象类。它不能单独工作;它必须被子 POM 继承。另一种看待超级 POM 文件的方式是,它是 Maven 分享默认配置的方式。
再次提醒,如果你查看超级 POM 的 <pluginManagement> 部分,如以下代码片段所示,你会注意到一个特定的插件工件仅通过其 artifactId 和 version 元素来识别。这与之前提到的内容相矛盾:一个特定的工件通过 groupId、artifactId 和 version 的组合来唯一标识。这是如何可能的?
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.3</version>
</plugin>
对于插件有一个例外。在 POM 文件中,对于插件不需要指定 groupId——它是可选的。默认情况下,Maven 使用 org.apache.maven.plugins 或 org.codehaus.mojo 作为 groupId。请查看 MAVEN_HOME/conf/settings.xml 中的以下部分。在这个文件中定义的所有内容都将对在相应机器上运行的所有的 Maven 构建全局有效。如果你想在用户级别(在多用户环境中)保留配置,你可以简单地从 MAVEN_HOME/conf 复制 settings.xml 文件到 USER_HOME/.m2。如果你想为插件查找添加额外的 groupId 元素,你必须取消注释以下部分并添加它们:
<!-- pluginGroups
| This is a list of additional group identifiers that
| will be searched when resolving plugins by their prefix, i.e.
| when invoking a command line like "mvn prefix:goal".
| Maven will automatically add the group identifiers
| "org.apache.maven.plugins" and "org.codehaus.mojo"
| if these are not already contained in the list.
|-->
<pluginGroups>
<!-- pluginGroup
| Specifies a further group identifier to use for plugin
| lookup.
<pluginGroup>com.your.plugins</pluginGroup>
-->
</pluginGroups>
注意
我们将在 第四章 Maven 插件 中详细讨论 Maven 插件。
父 POM
当我们处理数百个 Maven 模块时,我们需要对项目进行结构化,以避免任何冗余或重复配置。否则,它将导致巨大的维护噩梦。让我们看看一些流行的开源项目。
WSO2 Carbon Turing 项目,可在 svn.wso2.org/repos/wso2/carbon/platform/branches/turing/ 找到,包含超过 1000 个 Maven 模块。任何从根目录下载源代码的人都应该能够使用所有组件构建它。根目录下的 pom.xml 文件充当模块聚合 POM。它定义了需要在 <modules> 元素下构建的所有 Maven 模块。每个模块元素定义了从根 POM 到相应 Maven 模块的相对路径。在定义的相对路径下需要另一个 POM 文件。WSO2 Carbon Turing 项目的根 POM 只充当聚合模块。它不与其他 Maven 模块建立任何父子关系。以下代码片段显示了根 pom.xml 中的模块配置:
<modules>
<module>parent</module>
<module>dependencies</module>
<module>service-stubs</module>
<module>components</module>
<module>platform-integration/clarity-framework</module>
<module>features</module>
<module>samples/shopping-cart</module>
<module>samples/shopping-cart-global</module>
</modules>
现在,让我们看看 parent 模块内部的 POM 文件。这个 POM 文件定义了插件仓库、分发仓库、插件和一系列属性。它没有依赖项,这是作为所有其他 Maven 子模块父 POM 的 POM 文件。父 POM 文件具有以下坐标:
<groupId>org.wso2.carbon</groupId>
<artifactId>platform-parent</artifactId>
<version>4.2.0</version>
<packaging>pom</packaging>
如果你查看components模块内的 POM 文件,它将parent/pom.xml作为父 Maven 模块。默认情况下,relativePath元素的值指向位于其上一级的pom.xml文件,即../pom.xml。然而,在这种情况下,它不是父 POM;因此,该元素的值必须被覆盖并设置为../parent/pom.xml,如下所示:
<groupId>org.wso2.carbon</groupId>
<artifactId>carbon-components</artifactId>
<version>4.2.0</version>
<parent>
<groupId>org.wso2.carbon</groupId>
<artifactId>platform-parent</artifactId>
<version>4.2.0</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
如果你进入components模块并运行mvn help:effective-pom,你会注意到一个有效的 POM 会聚合parent/pom.xml和components/pom.xml中定义的配置。父 POM 文件有助于将公共配置元素传播到下游 Maven 模块,它可以向上传播到许多级别。components/pom.xml文件作为其级别以下 Maven 模块的父 POM。例如,让我们看看下面的components/identity/pom.xml文件。它引用了components/pom.xml文件作为其父文件。请注意,在这里我们不需要使用relativePath元素,因为相应的父 POM 位于默认位置:
<groupId>org.wso2.carbon</groupId>
<artifactId>identity</artifactId>
<version>4.2.0</version>
<parent>
<groupId>org.wso2.carbon</groupId>
<artifactId>carbon-components</artifactId>
<version>4.2.0</version>
</parent>
注意
POM 文件中所有元素的完整列表在maven.apache.org/ref/3.3.3/maven-model/maven.html中详细解释。
管理 POM 依赖项
在一个拥有数百个 Maven 模块的大型开发项目中,管理依赖项可能是一项危险的任务。有两种有效的方法来管理依赖项:POM 继承和依赖分组。使用 POM 继承时,父 POM 必须在dependencyManagement部分定义其子模块使用的所有公共依赖项。这样,我们可以避免所有重复的依赖项。此外,如果我们必须更新给定依赖项的版本,我们只需在一个地方进行更改。让我们以之前讨论的 WSO2 Carbon Turing 项目为例。让我们看看parent/pom.xml中的dependencyManagement部分(这里只显示了 POM 文件的一部分):
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-transport-mail</artifactId>
<version>${axis2-transports.version}</version>
</dependency>
<dependency>
<groupId>org.apache.ws.commons.axiom.wso2</groupId>
<artifactId>axiom</artifactId>
<version>${axiom.wso2.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
注意
要了解更多关于依赖项管理的信息,请参阅依赖机制简介。
让我们来看看identity/org.wso2.carbon.identity.core/4.2.3/pom.xml文件中的dependency部分,它继承自components/pom.xml。在这里,你将只能看到给定依赖项的groupId和artifactId,而不是version。每个依赖项的版本通过父 POM 中的dependencyManagement部分进行管理。如果任何子 Maven 模块想要覆盖继承的依赖项的版本,它只需简单地添加version元素:
<dependencies>
<dependency>
<groupId>org.apache.axis2.wso2</groupId>
<artifactId>axis2</artifactId>
</dependency>
<dependency>
<groupId>org.apache.ws.commons.axiom.wso2</groupId>
<artifactId>axiom</artifactId>
</dependency>
</dependencies>
在这里需要强调的另一个最佳实践是,在父 POM 文件中指定依赖项版本的方式,如下所示:
<version>${axiom.wso2.version}</version>
与在 dependency 元素内部指定版本号不同,这里,我们将它提取出来,并将版本表示为一个属性。该属性的值定义在父 POM 的 properties 部分下,如下所示。这使得 POM 维护变得极其简单:
<properties>
<axis2.wso2.version>1.6.1.wso2v10</axis2.wso2.version>
</properties>
管理依赖项的第二种方法是通过依赖项分组。所有常见的依赖项都可以组合到一个单独的 POM 文件中。这种方法比 POM 继承要好得多。在这里,你不需要添加对单个依赖项的引用。让我们通过一个简单的例子来了解。首先,我们需要将所有依赖项逻辑上组合到一个单独的 POM 文件中。
Apache Axis2 是一个开源的 SOAP 引擎。为了构建 Axis2 客户端,你需要将以下所有依赖项添加到你的项目中:
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-kernel</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-adb</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-transport-http</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-transport-local</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-xmlbeans</artifactId>
<version>1.6.2</version>
</dependency>
注意
如果你拥有多个 Axis2 客户端模块,在每个模块中,你都需要复制所有这些依赖项。Apache Axis2 项目的完整源代码可在 svn.apache.org/viewvc/axis/axis2/java/core/trunk/modules/ 找到。
为了避免依赖重复,我们可以创建一个包含之前提到的五个依赖项的 Maven 模块,如下所示的项目。请确保将 packaging 元素的值设置为 pom:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>axis2-client</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-kernel</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-adb</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-transport-http</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-transport-local</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-xmlbeans</artifactId>
<version>1.6.2</version>
</dependency>
</dependencies>
</project>
现在,在你的所有 Axis2 客户端项目中,你只需要添加对 com.packt.axis2-client 模块的依赖项,如下所示:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>my-axis2-client</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>com.packt</groupId>
<artifactId>axis2-client</artifactId>
<version>1.0.0</version>
<type>pom<type>
</dependency>
</dependencies>
</project>
小贴士
请确保在 dependency 元素下将 type 元素的值设置为 pom,因为我们在这里引用的是 pom 打包的依赖项。如果它被忽略,Maven 默认将寻找具有 jar 打包的工件:
传递依赖项
传递依赖项功能是在 Maven 2.0 中引入的,它自动识别你的项目依赖项的依赖项,并将它们全部纳入你的项目构建路径。以下是一个 POM 的例子。它只有一个依赖项:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>jose</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>2.26</version>
</dependency>
</dependencies>
</project>
如果你尝试使用 mvn eclipse:eclipse 命令从之前的 POM 文件创建 Eclipse 项目,它将生成以下 .classpath 文件。在那里你可以看到,除了 nimbus-jose-jwt-2.26.jar 文件外,还增加了三个 JAR 文件。这些是 nimbus-jose-jwt 依赖项的传递依赖项:
<classpath>
<classpathentry kind="src" path="src/main/java" including="**/*.java"/>
<classpathentry kind="output" path="target/classes"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="var" path="M2_REPO/com/nimbusds/nimbus-jose-jwt/2.26/nimbus-jose-jwt-2.26.jar"/>
<classpathentry kind="var" path="M2_REPO/net/jcip/jcip-annotations/1.0/jcip-annotations-1.0.jar"/>
<classpathentry kind="var" path="M2_REPO/net/minidev/json-smart/1.1.1/json-smart-1.1.1.jar"/>
<classpathentry kind="var" path="M2_REPO/org/bouncycastle/bcprov-jdk15on/1.50/bcprov-jdk15on-1.50.jar"/>
</classpath>
如果你查看 nimbus-jose-jwt 项目的 POM 文件,你将看到之前提到的传递依赖项被定义为依赖项。Maven 没有对传递依赖项设置限制。一个传递依赖项可能引用另一个传递依赖项,并且如果没有找到循环依赖项,它可以无限地继续下去。
如果不谨慎使用,传递依赖项也可能带来一些麻烦。如果我们以之前讨论过的同一个 Maven 模块为例,并在src/main/java目录内有以下 Java 代码,它将编译得很好,没有任何错误。这只有一个依赖项——nimbus-jose-jwt-2.26.jar。然而,net.minidev.json.JSONArray类来自传递依赖项,即json-smart-1.1.1.jar。构建工作正常,因为 Maven 将所有传递依赖项放入项目构建路径中。一切都会运行得很好,直到有一天,你更新了nimbus-jose-jwt的版本,而新版本引用了一个与你的代码不兼容的新版本的json-smart JAR。这可能会轻易破坏你的构建,或者可能导致测试用例失败。这会带来风险,找到根本原因将是一场噩梦。
以下 Java 代码使用了来自json-smart-1.1.1.jar的JSONArray类:
import net.minidev.json.JSONArray;
import com.nimbusds.jwt.JWTClaimsSet;
public class JOSEUtil {
public static void main(String[] args) {
JWTClaimsSet jwtClaims = new JWTClaimsSet();
JSONArray jsonArray = new JSONArray();
jsonArray.add("maven-book");
jwtClaims.setIssuer("https://packt.com");
jwtClaims.setSubject("john");
jwtClaims.setCustomClaim("book", jsonArray);
}
}
为了避免这样的噩梦,你需要遵循一个简单的经验法则。如果你在 Java 类中有任何import语句,你需要确保对应的依赖项 JAR 文件被添加到项目的 POM 文件中。
Maven 依赖项插件可以帮助你找到你的 Maven 模块中的此类不一致性。运行以下命令并观察其输出:
$ mvn dependency:analyze
[INFO] --- maven-dependency-plugin:2.8:analyze (default-cli) @ jose ---
[WARNING] Used undeclared dependencies found:
[WARNING] net.minidev:json-smart:jar:1.1.1:compile
注意前一个输出中的两个警告。它清楚地说明我们有一个未声明的依赖项json-smart jar。
注意
Maven 依赖项插件有几个目标来查找你在管理依赖项时的不一致性和可能的漏洞。有关更多详细信息,请参阅maven.apache.org/plugins/maven-dependency-plugin/。
依赖项范围
Maven 定义了以下六个范围类型。如果给定依赖项没有定义scope元素,则将应用默认范围——compile。
-
compile:这是默认范围。在compile范围内定义的任何依赖项都将可在所有类路径中找到。它将被打包到 Maven 项目产生的最终工件中。如果你正在构建 WAR 类型工件,那么带有compile范围的引用 JAR 文件将嵌入到 WAR 文件本身中。 -
provided:这个范围期望相应的依赖项要么由 JDK 提供,要么由运行应用程序的容器提供。最好的例子是 servlet API。任何带有provided范围的依赖项都将可在构建时类路径中找到,但它不会被打包到最终工件中。如果是一个 WAR 文件,servlet API 将在构建时类路径中可用,但不会打包到 WAR 文件中。请参见以下provided范围的示例:<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> -
runtime:在runtime作用域下定义的依赖项仅在运行时可用,而不是在构建时类路径中。这些依赖项将被打包到最终工件中。你可能有一个在运行时与 MySQL 数据库通信的基于 Web 的应用程序。你的代码对 MySQL 数据库驱动没有硬依赖。代码是针对 Java JDBC API 编写的,并且在构建时不需要 MySQL 数据库驱动。然而,在运行时,它需要驱动程序与 MySQL 数据库通信。为此,驱动程序应该打包到最终工件中。 -
test:依赖项仅用于测试编译(例如 JUnit 和 TestNG),并且必须在test作用域下定义。这些依赖项不会打包到最终工件中。 -
system:这与provided作用域非常相似。唯一的区别是,在使用system作用域时,你需要告诉 Maven 如何找到它。当你在 Maven 仓库中没有找到所引用的依赖时,系统依赖很有用。使用这种方式,你需要确保所有系统依赖都能与源代码本身一起下载。总是建议避免使用系统依赖。以下代码片段显示了如何定义一个系统依赖:<dependency> <groupId>com.packt</groupId> <artifactId>jose</artifactId> <version>1.0.0</version> <scope>system</scope> <systemPath>${basedir}/lib/jose.jar</systemPath> </dependency>注意
basedir是 Maven 中定义的一个内置属性,用于表示具有相应 POM 文件的目录。 -
import:这仅适用于在dependencyManagement部分定义的依赖项,其打包类型为pom。让我们看一下以下 POM 文件;它已将打包类型定义为pom:<project> <modelVersion>4.0.0</modelVersion> <groupId>com.packt</groupId> <artifactId>axis2-client</artifactId> <version>1.0.0</version> <packaging>pom</packaging> <dependencyManagement> <dependencies> <dependency> <groupId>org.apache.axis2</groupId> <artifactId>axis2-kernel</artifactId> <version>1.6.2</version> </dependency> <dependency> <groupId>org.apache.axis2</groupId> <artifactId>axis2-adb</artifactId> <version>1.6.2</version> </dependency> </dependencies> </dependencyManagement> </project>现在,从不同的 Maven 模块中,我们在上一个模块的
dependencyManagement部分添加了一个依赖项,作用域值设置为import,类型值设置为pom:<project> <modelVersion>4.0.0</modelVersion> <groupId>com.packt</groupId> <artifactId>my-axis2-client</artifactId> <version>1.0.0</version> <dependencyManagement> <dependencies> <dependency> <groupId>com.packt</groupId> <artifactId>axis2-client</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <project>现在,如果我们对上面的 POM 文件运行
mvn help:effective-pom,我们会看到第一个依赖项被导入如下:<dependencyManagement> <dependencies> <dependency> <groupId>org.apache.axis2</groupId> <artifactId>axis2-kernel</artifactId> <version>1.6.2</version> </dependency> <dependency> <groupId>org.apache.axis2</groupId> <artifactId>axis2-adb</artifactId> <version>1.6.2</version> </dependency> </dependencies> </dependencyManagement>
可选依赖
假设我们有一个必须与两个不同的 OSGi 运行时一起工作的 Java 项目。我们几乎将所有代码都写到了 OSGi API 中,但代码中有某些部分消耗了 OSGi 运行时特定的 API。当应用程序运行时,只有与底层 OSGi 运行时相关的代码路径会被执行,而不是两者都会执行。这产生了在构建时需要两个 OSGi 运行时 JAR 的需求。然而,在运行时,我们不需要两个代码执行路径,只需要与相应的 OSGi 运行时相关的那个。我们可以通过以下所示的可选依赖来满足这些要求:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>osgi.client</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.eclipse.equinox</groupId>
<artifactId>osgi</artifactId>
<version>3.1.1</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-core</artifactId>
<version>3.0.0-incubating</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
</dependencies>
<project>
对于任何需要com.packt.osgi.client在 Equinox OSGi 运行时中工作的客户端项目,它必须显式地添加对 Equinox JAR 文件的依赖,如下面的代码所示:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>my.osgi.client</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.eclipse.equinox</groupId>
<artifactId>osgi</artifactId>
<version>3.1.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.packt</groupId>
<artifactId>osgi.client</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
依赖排除
依赖排除有助于避免获取一组选定的传递依赖。比如说,我们有一个包含两个依赖项的以下 POM 文件:一个用于 nimbus-jose-jwt,另一个用于 json-smart 艺术品:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>jose</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>2.26</version>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
<version>1.0.9</version>
</dependency>
</dependencies>
</project>
如果你尝试在先前的 POM 文件上运行 mvn eclipse:eclipse,你会看到以下 .classpath 文件,它依赖于 json-smart 文件的 1.0.9 版本,正如预期的那样:
<classpathentry kind="var" path="M2_REPO/net/minidev/json-smart/1.0.9/json-smart-1.0.9.jar"/>
假设我们还有一个项目,它引用了相同的 nimbus-jose-jwt 艺术品,以及一个更新的 json-smart JAR 文件版本:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>jose.ext</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>2.26</version>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
</project>
如果你尝试在先前的 POM 文件上运行 mvn eclipse:eclipse,你会看到以下 .classpath 文件,它依赖于 json-smart 艺术品的 1.1.1 版本:
<classpathentry kind="var" path="M2_REPO/net/minidev/json-smart/1.1.1/json-smart-1.1.1.jar"/>
尽管如此,我们并没有看到问题。现在,假设我们构建了一个包含对先前 Maven 模块依赖的 WAR 文件:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>jose.war</artifactId>
<version>1.0.0</version>
<version>war</version>
<dependencies>
<dependency>
<groupId>com.packt</groupId>
<artifactId>jose</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.packt</groupId>
<artifactId>jose.ext</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>
一旦在 WEB-INF/lib 内部创建了 WAR 文件,我们只能看到 json-smart JAR 文件的 1.1.1 版本。这是 com.packt.jose.ext 项目的传递依赖。可能存在一种情况,WAR 文件在运行时不需要 1.1.1 版本,而是需要 1.0.9 版本。为了实现这一点,我们需要从 com.packt.jose.ext 项目中排除 json-smart JAR 文件的 1.1.1 版本,如下面的代码所示:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>jose.war</artifactId>
<version>1.0.0</version>
<version>war</version>
<dependencies>
<dependency>
<groupId>com.packt</groupId>
<artifactId>jose</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.packt</groupId>
<artifactId>jose.ext</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
现在,如果你查看 WEB-INF/lib 目录,你将只会看到 json-smart JAR 文件的 1.0.9 版本。
摘要
在本章中,我们围绕 Maven POM 进行了讨论,以及如何遵循行业广泛接受的最佳实践来避免维护噩梦。POM 文件的关键元素、POM 层次和继承、管理依赖以及相关主题在这里都有涉及。
在下一章中,我们将探讨 Maven 架构。
第三章。Maven 原型
“原型”这个词的根源在希腊文学中。它来源于两个希腊词,archein和typos。其中 archein 意味着原始或古老,而 typos 意味着模式。
“原型”这个词意味着原始模式。著名心理学家卡尔·古斯塔夫·荣格在心理学中引入了原型概念。荣格认为有 12 种不同的原型代表了人类的动机,并且他进一步将它们分为三类:自我、灵魂和自我。天真的人、普通人、英雄和照顾者属于自我类型。探险家、叛逆者、恋人和创造者属于灵魂类型。自我类型包括小丑、智者、魔术师和统治者。Maven 原型的概念与荣格在心理学中解释的并没有太大的出入。
下图显示了 Maven 项目、项目原型以及从原型生成的项目之间的关系:

当我们创建一个 Java 项目时,我们需要根据项目的类型以不同的方式来构建它。如果是一个 Java EE Web 应用程序,那么我们需要有一个WEB-INF目录和一个web.xml文件。如果是一个 Maven 插件项目,我们需要有一个扩展自org.apache.maven.plugin.AbstractMojo的Mojo类。由于每种类型的项目都有自己的预定义结构,为什么每个人都必须一次又一次地构建相同结构呢?为什么不从模板开始呢?每个项目都可以有自己的模板,开发者可以根据自己的需求扩展模板。Maven 原型解决了这个问题。每个原型都是一个项目模板。
注意
Maven 原型的列表可以在maven-repository.com/archetypes找到。
在本章中,我们将讨论以下主题:
-
Maven 原型插件
-
最常用的原型
原型快速入门
Maven archetype 本身就是一个插件。我们将在第四章Maven 插件中详细讨论插件。archetype插件的generate目标被用来从一个原型中生成一个 Maven 项目。让我们从一个简单的例子开始:
$ mvn archetype:generate
-DgroupId=com.packt.samples
-DartifactId=com.packt.samples.archetype
-Dversion=1.0.0
-DinteractiveMode=false
这个命令将调用 Maven archetype插件的generate目标来创建一个简单的 Java 项目。你会看到已经创建了一个包含示例 POM 文件的项目结构。根目录或基本目录的名称是从artifactId参数的值派生出来的:
com.packt.samples.archetype
|-pom.xml
|-src
|-main/java/com/packt/samples/App.java
|-test/java/com/packt/samples/AppTest.java
示例 POM 文件将只包含对junit JAR 文件的依赖,范围是test:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.samples</groupId>
<artifactId>com.packt.samples.archetype</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<name>com.packt.samples.archetype</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
生成的App.java类将包含以下模板代码。包名是从提供的groupId参数派生出来的。如果我们想将不同的值作为包名,那么我们需要在命令中传递这个值,例如-Dpackage=com.packt.samples.application:
package com.packt.samples;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello World!" );
}
}
这是开始 Maven 项目的最简单方式。在之前的示例中,我们通过设置 interactiveMode=false 使用了非交互模式。这将强制插件使用我们在命令本身中传递的任何值,以及默认值。
要在交互模式下调用插件,只需输入 mvn archetype:generate。这将提示用户输入,随着插件的执行进行。第一个提示是要求输入筛选条件或原型的类型编号。筛选条件可以指定为 [groupdId:]artifactId 的格式,如下所示:
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 471:
当你输入筛选条件时,例如,org.apache.maven.archetypes:maven-archetype-quickstart,插件将显示与之关联的数字,如下所示:
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 471: org.apache.maven.archetypes:maven-archetype-quickstart
Choose archetype:
1: remote -> org.apache.maven.archetypes:maven-archetype-quickstart (An archetype which contains a sample Maven project.)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 1:
在这种情况下,只有一个 archetype 与筛选条件匹配,与之关联的数字是 1。如果你在上一输出的最后一行按 Enter 键,或者只输入 1,插件将开始使用 org.apache.maven.archetypes:maven-archetype-quickstart 原型。
你可能已经注意到的某件事是,一旦你输入 mvn archetype:generate,插件就会显示一个由插件支持的 Maven 原型长列表,每个原型都有一个与之关联的数字。你可以通过在命令本身中指定筛选条件来避免这个长列表,如下所示:
$ mvn archetype:generate
–Dfilter=org.apache.maven.archetypes:maven-archetype-quickstart
Choose archetype:
1: remote -> org.apache.maven.archetypes:maven-archetype-quickstart (An archetype which contains a sample Maven project.)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 1:
批处理模式
archetype 插件可以通过将 interactiveMode 参数设置为 false 或传递 -B 作为参数来在批处理模式下运行。在批处理模式下,你需要明确指定你将要使用的原型,使用 archetypeGroupId、archetypeArtifactId 和 archetypeVersion 参数。你还需要明确识别结果工件,使用 groupId、artifactId、version 和 package 参数,如下所示:
$ mvn archetype:generate -B
-DarchetypeGroupId=org.apache.maven.archetypes
-DarchetypeArtifactId=maven-archetype-quickstart
-DarchetypeVersion=1.0
-DgroupId=com.packt.samples
-DartifactId=com.packt.samples.archetype
-Dversion=1.0.0
-Dpackage=1.5
任何好奇的头脑现在都应该提出一个非常有效的问题。
在非交互模式下,我们在第一个示例中并没有输入任何筛选条件或为原型提供任何 Maven 坐标。那么,插件是如何知道原型的呢?当没有指定原型时,插件将使用默认的一个,即 org.apache.maven.archetypes:maven-archetype-quickstart。
原型目录
插件是如何找到系统中所有可用的原型的?当你只输入 mvn archetype:generate 时,插件会显示一个供用户选择的原型列表。完整的列表大约有 1100 个,但这里只展示了前 10 个:
1: remote -> br.com.ingenieux:elasticbeanstalk-service-webapp-archetype (A Maven Archetype Encompassing RestAssured, Jetty, Jackson, Guice and Jersey for Publishing JAX-RS-based Services on AWS' Elastic Beanstalk Service)
2: remote -> br.com.ingenieux:elasticbeanstalk-wrapper-webapp-archetype (A Maven Archetype Wrapping Existing war files on AWS' Elastic Beanstalk Service)
3: remote -> br.com.otavio.vraptor.archetypes:vraptor-archetype-blank (A simple project to start with VRaptor 4)
4: remote -> br.gov.frameworkdemoiselle.archetypes:demoiselle-html-rest (Archetype for web applications (HTML + REST) using Demoiselle Framework)
5: remote -> br.gov.frameworkdemoiselle.archetypes:demoiselle-jsf-jpa (Archetype for web applications (JSF + JPA) using Demoiselle Framework)
6: remote -> br.gov.frameworkdemoiselle.archetypes:demoiselle-minimal (Basic archetype for generic applications using Demoiselle Framework)
7: remote -> br.gov.frameworkdemoiselle.archetypes:demoiselle-vaadin-jpa (Archetype for Vaadin web applications)
8: remote -> ch.sbb.maven.archetypes:iib9-maven-projects (IBM Integration Bus 9 Maven Project Structure)
9: remote -> ch.sbb.maven.archetypes:wmb7-maven-projects (WebSphere Message Broker 7 Maven Project Structure)
10: remote -> co.ntier:spring-mvc-archetype (An extremely simple Spring MVC archetype, configured with NO XML.)
回到最初的问题,插件是如何找到这些不同原型的详细信息的?
archetype 插件在插件本身附带的一个内部目录中维护不同原型的详细信息。原型目录只是一个 XML 文件。以下展示了 archetype 插件的内部目录:
<archetype-catalog>
<!-- Internal archetype catalog listing archetypes from the Apache Maven project. -->
<archetypes>
<archetype>
<groupId>org.apache.maven.archetypes</groupId>
<artifactId>maven-archetype-archetype</artifactId>
<version>1.0</version>
<description>An archetype which contains a sample archetype.</description>
</archetype>
<archetype>
<groupId>org.apache.maven.archetypes</groupId>
<artifactId>maven-archetype-j2ee-simple</artifactId>
<version>1.0</version>
<description>An archetype which contains a simplifed sample J2EE application.</description>
</archetype>
<archetype>
<groupId>org.apache.maven.archetypes</groupId>
<artifactId>maven-archetype-plugin</artifactId>
<version>1.2</version>
<description>An archetype which contains a sample Maven plugin.</description>
</archetype>
<archetype>
<groupId>org.apache.maven.archetypes</groupId>
<artifactId>maven-archetype-plugin-site</artifactId>
<version>1.1</version>
<description>An archetype which contains a sample Maven plugin site.This archetype can be layered upon an existing Maven plugin project.</description>
</archetype>
<archetype>
<groupId>org.apache.maven.archetypes</groupId>
<artifactId>maven-archetype-portlet</artifactId>
<version>1.0.1</version>
<description>An archetype which contains a sample JSR-268 Portlet.</description>
</archetype>
<archetype>
<groupId>org.apache.maven.archetypes</groupId>
<artifactId>maven-archetype-profiles</artifactId>
<version>1.0-alpha-4</version>
<description></description>
</archetype>
<archetype>
<groupId>org.apache.maven.archetypes</groupId>
<artifactId>maven-archetype-quickstart</artifactId>
<version>1.1</version>
<description>An archetype which contains a sample Maven project.</description>
</archetype>
<archetype>
<groupId>org.apache.maven.archetypes</groupId>
<artifactId>maven-archetype-site</artifactId>
<version>1.1</version>
<description>An archetype which contains a sample Maven site which demonstrates some of the supported document types like APT, XDoc, and FML and demonstrates how to i18n your site. This archetype can be layered upon an existing Maven project.</description>
</archetype>
<archetype>
<groupId>org.apache.maven.archetypes</groupId>
<artifactId>maven-archetype-site-simple</artifactId>
<version>1.1</version>
<description>An archetype which contains a sample Maven site.</description>
</archetype>
<archetype>
<groupId>org.apache.maven.archetypes</groupId>
<artifactId>maven-archetype-webapp</artifactId>
<version>1.0</version>
<description>An archetype which contains a sample Maven Webapp project.</description>
</archetype>
</archetypes>
</archetype-catalog>
小贴士
除了内部目录外,你还可以维护一个local架构原型目录。它位于USER_HOME/.m2/archetype-catalog.xml,默认情况下,它是一个空文件。
注意
还有一个远程目录可供使用,地址为repo1.maven.org/maven2/archetype-catalog.xml。
默认情况下,archetype插件将从local和remote目录中加载所有可用的架构原型。如果我们回到插件显示的架构列表,并输入mvn archetype:generate,然后通过查看每个条目,我们可以确定给定的架构是从internal、local还是remote目录加载的。
例如,以下架构是从remote目录加载的:
1: remote -> br.com.ingenieux:elasticbeanstalk-service-webapp-archetype (A Maven Archetype Encompassing RestAssured, Jetty, Jackson, Guice and Jersey for Publishing JAX-RS-based Services on AWS' Elastic Beanstalk Service)
如果你想强制archetype插件只列出内部目录中的所有架构原型,那么你需要使用以下命令:
$ mvn archetype:generate -DarchetypeCatalog=internal
要只列出local目录中的所有架构原型,你需要使用以下命令:
$ mvn archetype:generate -DarchetypeCatalog=local
要列出internal、local和remote目录中的所有架构原型,你需要使用以下命令:
$ mvn archetype:generate -DarchetypeCatalog=internal,local,remote
构建架构原型目录
除了internal、local和remote目录外,你还可以构建自己的目录。比如说,你已经开发了自己的 Maven 架构原型集,并需要构建一个目录,可以通过公开托管与他人共享。一旦构建了架构原型,它们将可在你的local Maven 仓库中找到。以下命令将遍历local Maven 仓库,并从所有可用的架构原型中构建一个架构原型目录。在这里,我们使用archetype插件的crawl目标:
$ mvn archetype:crawl -Dcatalog=my-catalog.xml
公开架构原型目录
为他们的项目开发原型的人会将它们列在公开的架构原型目录中。以下列表显示了一些公开可用的 Maven 架构原型目录:
-
Fuse:可以在
repo.fusesource.com/nexus/content/groups/public/archetype-catalog.xml找到 Fuse 架构原型目录。 -
Java.net:可以在
java.net/projects/maven2-repository/sources/svn/content/trunk/repository/archetype-catalog.xml找到 Java.net 架构原型目录。 -
Cocoon:可以在
cocoon.apache.org/archetype-catalog.xml找到 Cocoon 架构原型目录。 -
MyFaces:可以在
myfaces.apache.org/archetype-catalog.xml找到 MyFaces 架构原型目录。 -
Apache Synapse:可以在
synapse.apache.org/archetype-catalog.xml找到 Apache Synapse 架构原型目录。
以 Apache Synapse 为例。Synapse 是一个开源的 Apache 项目,用于构建 企业级 服务总线(ESB)。以下命令使用 Apache Synapse 架构生成 Maven 项目:
$ mvn archetype:generate
-DgroupId=com.packt.samples
-DartifactId=com.packt.samples.synapse
-Dversion=1.0.0
-Dpackage=com.packt.samples.synapse.application
-DarchetypeCatalog=http://synapse.apache.org
-DarchetypeGroupId=org.apache.synapse
-DarchetypeArtifactId=synapse-package-archetype
-DarchetypeVersion=2.0.0
-DinteractiveMode=false
之前的命令将生成以下目录结构。如果您查看 pom.xml 文件,您会注意到它包含构建 Synapse 项目所需的所有必要指令以及所需的依赖项:
com.packt.samples.synapse
|-pom.xml
|-src/main/assembly/bin.xml
|-conf/log4j.properties
|-repository/conf
|-axis2.xml
|-synapse.xml
让我们看看之前我们用来使用 Synapse 架构构建项目的 Maven 命令。最重要的参数是 archetypeCatalog。archetypeCatalog 参数的值可以直接指向 archetype-catalog.xml 文件或指向包含 archetype-catalog.xml 文件的目录。以下配置显示了与 Synapse 架构对应的 archetype-catalog.xml 文件。它只有一个架构,但有两个不同的版本:
<archetype-catalog>
<archetypes>
<archetype>
<groupId>org.apache.synapse</groupId>
<artifactId>synapse-package-archetype</artifactId>
<version>1.3</version>
<repository>http://repo1.maven.org/maven2</repository>
<description>Create a Synapse 1.3 custom package</description>
</archetype>
<archetype>
<groupId>org.apache.synapse</groupId>
<artifactId>synapse-package-archetype</artifactId>
<version>2.0.0</version>
<repository>
http://people.apache.org/repo/m2-snapshot-repository
</repository>
<description>Create a Synapse 2.0.0 custom package</description>
</archetype>
</archetypes>
</archetype-catalog>
注意
archetypeCatalog 参数的值可以是一个以逗号分隔的列表,其中每个项目指向一个 archetype-catalog.xml 文件或指向一个包含 archetype-catalog.xml 的目录。默认值是 remote 和 local,其中架构从 local 仓库和 remote 仓库加载。如果您想从本地文件系统加载 archetype-catalog.xml 文件,那么您需要将文件的绝对路径前缀为 file://。local 的值只是 file://~/.m2/archetype-catalog.xml 的快捷方式。
在之前的 Maven 命令中,我们使用了非交互模式的 archetype 插件,因此我们必须非常具体地指定所需的架构来生成 Maven 项目。这是通过以下三个参数完成的。这三个参数的值必须与关联的 archetype-catalog.xml 文件中定义的相应元素相匹配:
-DarchetypeGroupId=org.apache.synapse
-DarchetypeArtifactId=synapse-package-archetype
-DarchetypeVersion=2.0.0
架构 - 目录.xml 的结构
我们已经浏览了一些示例 archetype-catalog.xml 文件及其用途。archetype-catalog.xml 文件的 XML 架构可在 maven.apache.org/xsd/archetype-catalog-1.0.0.xsd 找到。以下是一个包含所有关键元素的 archetype-catalog.xml 文件骨架:
<archetype-catalog>
<archetypes>
<archetype>
<groupId></groupId>
<artifactId></artifactId>
<version></version>
<repository></repository>
<description></description>
</archetype>
...
</archetypes>
</archetype-catalog>
archetypes父元素可以包含一个或多个archetype子元素。每个archetype元素都应该唯一标识与它对应的 Maven 工件。这是通过组合工件的groupId、artifactId和version元素来实现的。这三个元素携带与我们讨论的 Maven 坐标相同的精确含义。description元素可以用来描述原型。描述元素的值将在archetype插件列出原型时显示。例如,当你输入mvn archetype:generate时,根据archetype-catalog.xml文件的模式生成的以下输出——groupId:artifactId (description):
Choose archetype:
1: remote -> org.apache.maven.archetypes:maven-archetype-quickstart (An archetype which contains a sample Maven project.)
每个archetype子元素都可以为repository元素携带一个值。这指示archetype插件在哪里找到相应的工件。当未指定值时,工件将从包含目录文件的仓库中加载。
原型插件的目标
到目前为止,在本章中,我们只讨论了archetype插件的generate和crawl目标。Maven 构建过程中的所有有用功能都是作为插件开发的。一个给定的 Maven 插件可以有多个目标,其中每个目标执行一个非常具体的任务。我们将在第四章“Maven 插件”中详细讨论插件。第四章。
以下目标与archetype插件相关:
-
archetype:generate:generate目标创建一个与所选原型对应的 Maven 项目。它接受archetypeGroupId、archetypeArtifactId、archetypeVersion、filter、interactiveMode、archetypeCatalog和baseDir参数。我们已经在详细讨论了几乎所有这些参数。 -
archetype:update-local-catalog:update-local-catalog目标必须在 Maven 原型项目上执行。这将使用新的原型更新local原型目录。local原型目录位于~/.m2/archetype-catalog.xml。 -
archetype:jar:jar目标必须在 Maven 原型项目上执行,这将从中创建一个 JAR 文件。它接受archetypeDirectory参数,其中包含类;它还接受finalName参数,即要生成的 JAR 文件的名称,以及outputDirectory参数,即最终输出被复制到的位置。 -
archetype:crawl:crawl目标会遍历一个本地或基于文件系统的 Maven 仓库(不是远程或通过 HTTP),并创建一个原型目录文件。它接受catalogFile作为参数(映射到catalog系统属性),即要创建的目录文件的名称。默认情况下,它会遍历localMaven 仓库,要覆盖位置,我们需要通过repository参数传递相应的仓库 URL。 -
archetype:create-from-project:create-from-project目标可以从现有项目创建一个架构项目。如果您将其与generate目标进行比较,那么实际上generate是从头开始创建一个与所选架构相对应的新 Maven 项目,而create-from-project则是从现有项目创建 Maven 架构项目。换句话说,create-from-project是从一个现有的 Maven 项目生成一个模板。 -
archetype:integration-test:integration-test目标将执行与 Maven 架构项目相关的集成测试。 -
archetype:help:help目标将显示与archetype插件相关的手册,列出所有可用的目标。如果您想获取所有目标的详细描述,请使用-Ddetail=true参数与命令一起使用。也可以为给定的目标获取帮助。例如,以下命令将显示与generate目标相关的帮助:$ mvn archetype:help -Ddetail=true -Dgoal=generate
使用架构插件的 Java EE Web 应用程序
如果您想从 Java EE Web 应用程序开始,可以使用 maven-archetype-webapp 架构来生成 Maven 项目骨架,如下所示:
$ mvn archetype:generate -B
-DgroupId=com.packt.samples
-DartifactId=my-webapp
-Dpackage=com.packt.samples.webapp
-Dversion=1.0.0
-DarchetypeGroupId=org.apache.maven.archetypes
-DarchetypeArtifactId=maven-archetype-webapp
-DarchetypeVersion=1.0
上述命令将生成以下目录结构。这里的一个问题是它没有在 src/main 之后立即创建 java 目录。如果您想添加任何 Java 代码,您需要确保首先创建一个 src/main/java 目录,并在其中创建您的 Java 包;否则,使用默认的配置设置,Maven 不会选择您的类进行编译。默认情况下,Maven 在 src/main/java 内查找源代码:
my-webapp
|-pom.xml
|-src/main/webapp
|-index.jsp
|-WEB-INF/web.xml
|- src/main/resources
maven-archetype-webapp 架构并不是唯一使用 archetype 插件生成 Java EE 项目的架构。Codehaus,一个构建开源项目的协作环境,也提供了一些架构来生成 Web 应用程序。以下示例使用了 Codehaus 的 webapp-javaee6 架构:
$ mvn archetype:generate -B
-DgroupId=com.packt.samples
-DartifactId=my-webapp
-Dpackage=com.packt.samples.webapp
-Dversion=1.0.0
-DarchetypeGroupId=org.codehaus.mojo.archetypes
-DarchetypeArtifactId=webapp-javaee6
-DarchetypeVersion=1.3
上述命令将生成以下目录结构。这解决了 maven-archetype-webapp 架构中的一个问题,并创建了 src/main/java 和 src/test/java 目录。这里唯一的问题是它没有创建 src/main/webapp/WEB-INF 目录,您需要手动创建它:
my-webapp
|-pom.xml
|-src/main/webapp/index.jsp
|-src/main/java/com/packt/samples/webapp/
|-src/test/java/com/packt/samples/webapp/
将 Web 应用程序部署到远程 Apache Tomcat 服务器
现在,我们已经使用 maven-archetype-webapp 或 webapp-javaee6 架构创建了一个模板 Web 应用程序。让我们看看如何从 Maven 本身将这个 Web 应用程序部署到远程 Apache Tomcat 应用程序服务器。大多数开发者都会喜欢这样做而不是手动复制。
注意
这假设您已经在环境中安装了 Apache Tomcat。如果没有,您可以从 tomcat.apache.org/download-70.cgi 下载 Tomcat 7.x 发行版并设置它。
要部署 Web 应用程序,请执行以下步骤:
-
由于我们打算将 Web 应用程序部署到远程 Tomcat 服务器,我们需要一个有效的用户账户,该账户具有部署 Web 应用程序的权限。将以下条目添加到
TOMCAT_HOME/conf/tomcat-users.xml文件下的tomcat-users根元素中。这将创建一个名为admin的用户,密码为password,并具有manager-gui和manager-script角色:<role rolename="manager-gui"/> <role rolename="manager-script"/> <user username="admin" password="password" roles="manager-gui,manager-script" /> -
现在,我们需要配置 Maven 以与远程 Tomcat 服务器通信。将以下配置添加到
USER_HOME/.m2/settings.xml文件下的servers元素中,如下所示:<server> <id>apache-tomcat</id> <username>admin</username> <password>password</password> </server> -
进入我们之前生成的模板 Web 应用程序的根目录(
my-webapp),然后向该目录下的pom.xml文件添加tomcat7-maven-plugin。完整的pom.xml文件将如下所示:<project > <modelVersion>4.0.0</modelVersion> <groupId>com.packt.samples</groupId> <artifactId>my-webapp</artifactId> <packaging>war</packaging> <version>1.0.0</version> <name>my-webapp Maven Webapp</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>my-webapp</finalName> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <url>http://localhost:8080/manager/text</url> <server>apache-tomcat</server> <path>/my-webapp</path> </configuration> </plugin> </plugins> </build> </project> -
使用以下 Maven 命令构建并部署示例 Web 应用程序到 Tomcat 服务器。一旦部署,你可以通过
http://localhost:8080/my-webapp/访问它:$ mvn clean install tomcat7:deploy -
要重新部署,使用以下命令:
$ mvn clean install tomcat7:redeploy -
要卸载,使用以下命令:
$ mvn clean install tomcat7:undeploy
使用原型插件构建 Android 移动应用程序
如果你是一名希望从骨架 Android 项目开始的 Android 应用程序开发者,你可以使用 akquinet 开发的android-quickstart原型,如下所示:
$ mvn archetype:generate -B
-DarchetypeGroupId=de.akquinet.android.archetypes
-DarchetypeArtifactId=android-quickstart
-DarchetypeVersion=1.0.4
-DgroupId=com.packt.samples
-DartifactId=my-android-app
-Dversion=1.0.0
此命令将生成以下骨架项目:
my-android-app
|-pom.xml
|-AndroidManifest.xml
|-android.properties
|-src/main/java/com/packt/samples/HelloAndroidActivity.java
|-res/drawable-hdpi/icon.png
|-res/drawable-ldpi/icon.png
|-res/drawable-mdpi/icon.png
|-res/layout/main.xml
|-res/values/strings.xml
|-assets
要构建 Android 骨架项目,从my-android-app目录运行以下 Maven 命令:
$ mvn clean install -Dandroid.sdk.path=/path/to/android/sdk
之前的命令看起来很简单,但它基于你的 Android SDK 版本;因此,你可能会遇到某些问题。以下是一些可能的问题和解决方案:
-
如果你向
android.sdk.path参数传递一个无效的值,你会看到以下错误:[ERROR] Failed to execute goal com.jayway.maven.plugins.android.generation2:maven-android-plugin:2.8.3:generate-sources (default-generate-sources) on project my-android-app: Execution default-generate-sources of goal com.jayway.maven.plugins.android.generation2:maven-android-plugin:2.8.3:generate-sources failed: Path "/Users/prabath/Downloads/adt-bundle-mac-x86_64-20140702/platforms" is not a directory.路径将指向 Android 的
sdk目录,并且就在这个目录下面,你会找到platforms目录。通过将android.sdk.path设置为正确的路径,你可以避免这个错误。 -
默认情况下,
android-quickstart原型假定 Android 平台为7。如果你的本地机器上安装的 Android 平台与此不同,你会看到以下错误:[ERROR] Failed to execute goal com.jayway.maven.plugins.android.generation2:maven-android-plugin:2.8.3:generate-sources (default-generate-sources) on project my-android-app: Execution default-generate-sources of goal com.jayway.maven.plugins.android.generation2:maven-android-plugin:2.8.3:generate-sources failed: Invalid SDK: Platform/API level 7 not available.要修复这个问题,打开
pom.xml文件,并使用<sdk><platform>20</platform></sdk>设置正确的平台版本。 -
默认情况下,
android-quickstart原型假定aapt工具位于sdk/platform-tools下。然而,随着最新sdks的更新,它被移动到了sdk/build-tools/android-4.4W;你将得到以下错误:[ERROR] Failed to execute goal com.jayway.maven.plugins.android.generation2:maven-android-plugin:2.8.3:generate-sources (default-generate-sources) on project my-android-app: Execution default-generate-sources of goal com.jayway.maven.plugins.android.generation2:maven-android-plugin:2.8.3:generate-sources failed: Could not find tool 'aapt'.要修复错误,你需要更新
maven-android-plugin的version和artifactId。打开
my-android-app目录中的pom.xml文件,并找到以下插件配置。将artifactId更改为android-maven-plugin,版本更改为4.0.0-rc.1,如下所示:<plugin> <groupId> com.jayway.maven.plugins.android.generation2 </groupId> <artifactId>android-maven-plugin</artifactId> <version>4.0.0-rc.1</version> <configuration></configuration> <extensions>true</extensions> </plugin>
一旦构建完成,android-maven-plugin将在target目录内生成my-android-app-1.0.0.apk和my-android-app-1.0.0.jar工件。
要将骨架 Android 应用程序(apk)部署到连接的设备上,请使用以下 Maven 命令:
$ mvn android:deploy -Dandroid.sdk.path=/path/to/android/sdk
使用架构插件生成的 EJB 归档
在这里,我们将讨论如何使用由 Codehaus 开发的 ejb-javaee6 架构创建 Maven 企业 JavaBeans(EJB)项目,Codehaus 是一个构建开源项目的协作环境:
$ mvn archetype:generate -B
-DgroupId=com.packt.samples
-DartifactId=my-ejbapp
-Dpackage=com.packt.samples.ejbapp
-Dversion=1.0.0
-DarchetypeGroupId=org.codehaus.mojo.archetypes
-DarchetypeArtifactId=ejb-javaee6
-DarchetypeVersion=1.5
上一个命令会生成以下骨架项目。您可以在 src/main/java/com/packt/samples/ejbapp/ 目录内创建您的 EJB 类:
my-ejbapp
|-pom.xml
|-src/main/java/com/packt/samples/ejbapp/
|-src/main/resources/META-INF/MANIFEST.MF
如果您查看 my-ejbapp 目录中的以下 pom.xml 文件,您会注意到内部使用了 maven-ejb-plugin 来生成 EJB 产物:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ejb-plugin</artifactId>
<version>2.3</version>
<configuration:
<ejbVersion>3.1</ejbVersion>
</configuration>
</plugin>
尽管我们已突出显示 ejb-javaee6,但它并不是生成 Maven EJB 项目的最佳选择。由 Oracle WebLogic 开发的 ejb-javaee6 架构生成的模板非常基础。Oracle WebLogic 开发了一个更好的 EJB 架构——basic-webapp-ejb。以下示例展示了如何使用 basic-webapp-ejb 架构:
$ mvn archetype:generate -B
-DarchetypeGroupId=com.oracle.weblogic.archetype
-DarchetypeArtifactId=basic-webapp-ejb
-DarchetypeVersion=12.1.3-0-0
-DgroupId=com.packt.samples
-DartifactId=my-ejbapp
-Dpackage=com.packt.samples.ejbapp
-Dversion=1.0.0
在执行上一个命令之前,还有更多作业要做。basic-webapp-ejb 架构在任何一个公共 Maven 仓库中都是不可用的。首先,您需要从 www.oracle.com/webfolder/technetwork/tutorials/obe/java/wls_12c_netbeans_install/wls_12c_netbeans_install.html 下载 WebLogic 发行版,然后按照 README.txt 文件中的说明进行本地安装。安装完成后,basic-webapp-ejb 架构和 weblogic-maven-plugin 可以安装到本地 Maven 仓库中,如下所示:
-
前往
wls12130/wlserver/server/lib目录并执行以下命令。这将使用 WebLogic JarBuilder 工具构建插件 JAR 文件:$ java -jar wljarbuilder.jar -profile weblogic-maven-plugin -
上一个命令将创建
weblogic-maven-plugin.jar文件。现在,我们需要将其提取出来以获取pom.xml文件。从wls12130/wlserver/server/lib目录执行以下命令:$ jar xvf weblogic-maven-plugin.jar -
现在,我们需要将
pom.xml文件复制到wls12130/wlserver/server/lib。从wls12130/wlserver/server/lib目录执行以下命令:$ cp META-INF/maven/com.oracle.weblogic/weblogic-maven-plugin/pom.xml . -
现在,我们可以将
weblogic-maven-plugin.jar安装到localMaven 仓库中。从wls12130/wlserver/server/lib目录执行以下命令:$ mvn install:install-file -Dfile=weblogic-maven-plugin.jar -DpomFile=pom.xml -
除了插件之外,我们还需要安装
basic-webapp-ejb架构。为此,请前往wls12130/oracle_common/plugins/maven/com/oracle/maven/oracle-maven-sync/12.1.3并执行以下两个命令。请注意,oracle_common是一个隐藏目录。如果您使用的是 WebLogic 的不同版本而不是 12.1.3,请使用与您的版本相关的数字:$ mvn install:install-file -DpomFile=oracle-maven-sync-12.1.3.pom -Dfile=oracle-maven-sync-12.1.3.jar $ mvn com.oracle.maven:oracle-maven-sync:push -Doracle-maven-sync.oracleHome=/Users/prabath/Downloads/wls12130 -Doracle-maven-sync.testingOnly=false
完成这些步骤后,您可以使用 WebLogic 的basic-webapp-ejb存档来生成 EJB 模板项目。请确保您有正确的archetypeVersion版本;这应该与您 WebLogic 发行版中包含的存档版本相匹配:
$ mvn archetype:generate -B
-DarchetypeGroupId=com.oracle.weblogic.archetype
-DarchetypeArtifactId=basic-webapp-ejb
-DarchetypeVersion=12.1.3-0-0
-DgroupId=com.packt.samples
-DartifactId=my-ejbapp
-Dpackage=com.packt.samples.ejbapp
-Dversion=1.0.0
此命令生成以下项目骨架:
my-ejbapp
|-pom.xml
|-src/main/java/com/packt/samples/ejbapp
|-entity/Account.java
|-service/AccountBean.java
|-service/AccountManager.java
|-service/AccountManagerImpl.java
|-interceptor/LogInterceptor.java
|-interceptor/OnDeposit.java
|-src/main/resources/META-INF/persistence.xml
|-src/main/scripts
|-src/main/webapp/WEB-INF/web.xml
|-src/main/webapp/WEB-INF/beans.xml
|-src/main/webapp/css/bootstrap.css
|-src/main/webapp/index.xhtml
|-src/main/webapp/template.xhtml
要打包 EJB 存档,请从my-ejbapp目录执行以下命令。这将生成target目录中的basicWebappEjb.war。现在,您可以将这个 WAR 文件部署到支持 EJB 的 Java EE 应用程序服务器中。
$ mvn package
使用存档插件的 JIRA 插件
JIRA 是由 Atlassian 开发的一个问题跟踪系统。它在许多开源项目中非常受欢迎。JIRA 的一个扩展点是它的插件。在这里,我们将看到如何使用 Atlassian 开发的jira-plugin-archetype生成骨架 JIRA 插件:
$ mvn archetype:generate -B
-DarchetypeGroupId=com.atlassian.maven.archetypes
-DarchetypeArtifactId=jira-plugin-archetype
-DarchetypeVersion=3.0.6
-DgroupId=com.packt.samples
-DartifactId=my-jira-plugin
-Dpackage=com.packt.samples.jira
-Dversion=1.0.0
-DarchetypeRepository=
http://repo.jfrog.org/artifactory/libs-releases/
此命令将生成以下项目模板:
my-jira-plugin
|-pom.xml
|-README
|-LICENSE
|-src/main/java/com/packt/samples/jira/MyPlugin.java
|-src/main/resources/atlassian-plugin.xml
|- src/test/java/com/packt/samples/jira/MyPluginTest.java
|-src/test/java/it/MyPluginTest.java
|-src/test/resources/TEST_RESOURCES_README
|-src/test/xml/TEST_XML_RESOURCES_README
使用存档插件构建的 Spring MVC 应用程序
Spring 模型视图控制器(MVC)是在 Spring 框架下开发的一个 Web 应用程序框架,Spring 是一个开源的应用程序框架,也是一个控制反转容器。在这里,我们将看到如何使用spring-mvc-quickstart存档生成模板 Spring MVC 应用程序。
注意
要了解更多关于 Spring MVC 框架的信息,请参阅docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html。
目前,spring-mvc-quickstart存档在任何一个公共 Maven 仓库中都不可用,因此我们必须从 GitHub 下载它并从源代码构建,如下所示:
$ git clone https://github.com/kolorobot/spring-mvc-quickstart-archetype.git
$ cd spring-mvc-quickstart-archetype
$ mvn clean install
一旦从源代码构建存档并在本地 Maven 仓库中可用,您可以通过执行以下命令来生成模板 Spring MVC 应用程序:
$ mvn archetype:generate -B
-DarchetypeGroupId=com.github.spring-mvc-archetypes
-DarchetypeArtifactId=spring-mvc-quickstart
-DarchetypeVersion=1.0.0-SNAPSHOT
-DgroupId=com.packt.samples
-DartifactId=my-spring-app
-Dpackage=com.packt.samples.spring
-Dversion=1.0.0
这将生成以下项目模板:
my-spring-app
|-pom.xml
|-src/main/java/com/packt/samples/spring/Application.java
|-src/main/webapp/WEB-INF/views
|-src/main/webapp/resources
|-src/main/resources
|-src/test/java/com/packt/samples/spring
|-src/test/resources
让我们看看如何通过 Maven 本身运行模板 Spring MVC 应用程序,并通过内嵌的 Tomcat。一旦服务器启动,您可以通过http://localhost:8080/my-spring-app浏览 Web 应用程序。可以通过tomcat7插件的run目标启动内嵌的 Tomcat,如下所示:
$ mvn test tomcat7:run
注意
关于tomcat7插件的更多详细信息可在tomcat.apache.org/maven-plugin-trunk/tomcat7-maven-plugin/找到。
摘要
在本章中,我们专注于 Maven 存档。Maven 存档提供了一种减少构建 Maven 项目重复工作的方法。有成千上万的存档可供公开使用,以帮助您构建不同类型的项目。本章涵盖了常用的一组存档。
在下一章中,我们将探讨 Maven 插件。
第四章。Maven 插件
Maven 的根源可以追溯到 Jakarta Turbine 项目,该项目最初是为了简化 Jakarta Turbine 的构建过程。Maven 的美妙之处在于其设计。它并不试图自己完成所有事情,而是委托给一个插件框架。当你从其网站下载 Maven 时,它只有核心框架,插件是按需下载的。构建过程中的所有有用功能都是作为 Maven 插件开发的。你还可以将 Maven 称为一个插件执行框架。以下图显示了 Maven 插件:

Maven 插件可以独立执行,也可以作为 Maven 生命周期的一部分执行。我们将在第五章构建生命周期中讨论 Maven 生命周期。
注意
Maven 构建生命周期由一系列定义良好的阶段组成。每个阶段都包含由 Maven 插件定义的一组目标,生命周期定义了执行顺序。Maven 提供了三个标准生命周期:default、clean和site。每个生命周期定义了自己的阶段集。
每个插件都有自己的目标集,每个目标都负责执行特定的操作。让我们看看如何执行 Maven clean插件的clean目标。clean目标将尝试清理工作目录和构建过程中创建的相关文件:
$ mvn clean:clean
注意
Maven 插件可以作为mvn plugin-prefix-name:goal-name执行。
同样的clean插件可以通过clean生命周期执行,如下命令所示:
$ mvn clean
Maven clean插件的clean目标与clean生命周期的clean阶段相关联。clean生命周期定义了三个阶段:pre-clean、clean和post-clean。生命周期中的一个阶段只是构建执行路径中的一个有序占位符。例如,clean生命周期中的clean阶段本身不能做任何事情。在 Maven 架构中,它有两个关键元素:名词和动词。与特定项目相关的名词和动词都在 POM 文件中定义。项目的名称、父项目的名称、依赖项和打包类型都是名词。插件将动词引入 Maven 构建系统,并通过其目标定义构建执行期间需要执行的操作。插件是一组目标。每个插件的目标都可以独立执行,也可以注册为 Maven 构建生命周期中某个阶段的一部分。这里的一个区别是,当你独立执行 Maven 插件时,它只运行命令中指定的目标;然而,当你将其作为生命周期的一部分运行时,Maven 将执行与相应生命周期关联的所有插件目标,直到指定的阶段(包括该阶段)。
当你输入 mvn clean 时,它将执行 clean 生命周期中定义的所有阶段,包括并限于 clean 阶段。不要混淆;在这个命令中,clean 不是生命周期的名称,而是阶段的名称。阶段名称恰好与生命周期名称相同只是一个巧合。在 Maven 中,你不能简单地通过名称执行生命周期——它必须是阶段名称。Maven 将找到相应的生命周期,并将执行其中直到给定阶段的所有阶段(包括该阶段)。
在本章中,我们将讨论以下主题:
-
常用 Maven 插件及其用法
-
插件发现和执行过程
常用 Maven 插件
Maven 插件主要在 Apache Maven 项目本身以及 Codehaus 和 Google Code 项目下开发。以下几节列出了常用 Maven 插件及其用法。
清理插件
如前所述,clean 插件执行 Maven clean 插件的 clean 目标,以删除在构建过程中创建的任何工作目录和其他资源,如下所示:
$ mvn clean:clean
Maven clean 插件也与 clean 生命周期相关联。如果你只是执行 mvn clean,clean 插件的 clean 目标将被执行。
你不需要在项目的 POM 文件中显式定义 Maven clean 插件。你的项目从 Maven 超级 POM 文件中继承它。在 第二章") 理解项目对象模型 (POM) 中,我们详细讨论了 Maven 超级 POM 文件。以下超级 POM 文件中的配置将 Maven clean 插件与所有 Maven 项目关联起来:
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>2.5</version>
<executions>
<execution>
<id>default-clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
注意
Maven default 生命周期包括以下阶段:validate、initialize、generate-sources、process-sources、generate-resources、process-resources、compile、process-classes、generate-test-sources、process-test-sources、generate-test-resources、process-test-resources、test-compile、process-testclasses、test、prepare-package、package、pre-integration-test、integration-test、post-integration-test、verify、install、deploy。
默认情况下,clean 插件的 clean 目标在 Maven clean 生命周期的 clean 阶段下运行。如果你的项目想要默认运行 clean 插件,那么你可以将其与 Maven default 生命周期的 initialize 阶段关联起来。你可以在应用程序 POM 文件中添加以下配置:
<project>
[...]
<build>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>2.5</version>
<executions>
<execution>
<id>auto-clean</id>
<phase>initialize</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
[...]
</project>
现在,当你执行 Maven default生命周期的任何阶段时,clean插件的clean目标将被执行;没有必要显式执行clean生命周期的clean阶段。例如,mvn install将在其initialize阶段运行clean目标。这样,你可以覆盖 Maven clean插件的默认行为。一个包含先前插件配置的完整 Maven 示例项目可在svn.wso2.org/repos/wso2/people/prabath/maven-mini/chapter04/jose找到。
编译器插件
compiler插件用于编译源代码。它有两个目标:compile和testCompile。compile目标绑定到 Maven default生命周期的compile阶段。当你输入mvn clean install时,Maven 将执行default生命周期中的所有阶段,直到install阶段,这包括compile阶段。这反过来将运行compiler插件的compile目标。
以下命令显示了如何单独执行compiler插件的compile目标。这将简单地编译你的源代码:
$ mvn compiler:compile
所有 Maven 项目都从父 POM 文件继承了compiler插件。如下配置所示,父 POM 定义了compiler插件。它将testCompile和compile目标与 Maven 的default生命周期的test-compile和compile阶段关联起来:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<executions>
<execution>
<id>default-testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
<execution>
<id>default-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
默认情况下,Maven 的compiler插件假定source和target元素都使用 JDK 1.5。JVM 通过source配置参数识别源代码的 Java 版本,通过target配置参数识别编译代码的版本。如果你想打破 Maven 的假设并指定自己的source和target版本,你需要在你的应用程序 POM 文件中覆盖compiler插件配置,如下所示:
<project>
[...]
<build>
[...]
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
[...]
</build>
[...]
</project>
你可以在compiler插件下的compilerArgument元素中传递任何参数,而不仅仅是源和目标元素。当 Maven 的compiler插件没有为相应的 JVM 参数定义元素时,这更有用。例如,相同的source和target值也可以以下方式传递:
<project>
[...]
<build>
[...]
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<compilerArgument>-source 1.7 –target 1.7</compilerArgument>
</configuration>
</plugin>
</plugins>
[...]
</build>
[...]
</project>
安装插件
install插件将最终项目工件部署到在MAVEN_HOME/conf/settings.xml下定义的local Maven 仓库中,默认位置是USER_HOME/.m2/repository。install插件的install目标绑定到 Maven default生命周期的install阶段。当你输入mvn clean install时,Maven 将执行default生命周期中的所有阶段,包括install阶段。
以下命令显示了如何单独执行install插件的install目标:
$ mvn install:install
所有 Maven 项目都从超级 POM 文件继承了install插件。如下配置所示,超级 POM 定义了install插件。它将install目标与 Maven default生命周期的install阶段关联起来:
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>default-install</id>
<phase>install</phase>
<goals>
<goal>install</goal>
</goals>
</execution>
</executions>
</plugin>
install插件的install目标在项目级别没有需要覆盖的配置。
deploy插件
deploy插件将最终项目工件部署到远程 Maven 仓库。deploy插件的deploy目标与default Maven 生命周期的deploy阶段相关联。要通过default生命周期部署工件,仅执行mvn clean install是不够的。必须执行mvn clean deploy。有什么猜测吗?
default Maven 生命周期的deploy阶段在install阶段之后。执行mvn clean deploy将执行default生命周期的所有阶段,包括deploy阶段,这也包括install阶段。以下命令展示了如何单独执行deploy插件的deploy目标:
$ mvn deploy:deploy
所有 Maven 项目都从超级 POM 文件继承了deploy插件。如下配置所示,超级 POM 定义了deploy插件。它将deploy目标与 Maven default生命周期的deploy阶段关联起来:
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<id>default-deploy</id>
<phase>deploy</phase>
<goals>
<goal>deploy</goal>
</goals>
</execution>
</executions>
</plugin>
在执行mvn deploy:deploy或mvn deploy之前,你需要在项目 POM 文件中的distributionManagement部分设置远程 Maven 仓库的详细信息,如下所示:
[...]
<distributionManagement>
<repository>
<id>wso2-maven2-repository</id>
<name>WSO2 Maven2 Repository</name>
<url>scp://dist.wso2.org/home/httpd/dist.wso2.org/maven2/</url>
</repository>
</distributionManagement>
[...]
在此示例中,Maven 通过scp连接到远程仓库。安全复制(scp)定义了在计算机网络中两个节点之间安全传输文件的方式,它建立在流行的 SSH 之上。为了验证远程服务器,Maven 提供了两种方式;一种基于用户名和密码,另一种基于 SSH 认证密钥。为了配置 Maven 仓库的用户名/密码凭据,我们需要在USER_HOME/.m2/settings.xml中添加以下<server>配置元素。id元素的值必须包含远程仓库主机名的值:
<server>
<id>dist.wso2.org</id>
<username>my_username</username>
<password>my_password</password>
</server>
如果远程仓库只支持 SSH 认证密钥,那么我们需要指定私钥的位置,如下所示:
<server>
<id>dist.wso2.org</id>
<username>my_username</username>
<privateKey>/path/to/private/key</privateKey>
</server>
deploy插件的deploy目标在项目级别没有需要覆盖的配置。
surefire插件
surefire插件将运行与项目关联的单元测试。surefire插件的test目标绑定到default Maven 生命周期的test阶段。当你输入mvn clean install时,Maven 将执行default生命周期中的所有阶段,包括install阶段,这也包括test阶段。
以下命令展示了如何执行surefire插件的test目标:
$ mvn surefire:test
所有 Maven 项目都从父 POM 文件继承 surefire 插件。如下所示配置,父 POM 定义了 surefire 插件。它将 test 目标与 Maven default 生命周期的 test 阶段相关联:
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<executions>
<execution>
<id>default-test</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>
由于 surefire 插件定义在父 POM 文件中,因此您不需要显式将其添加到您的应用程序 POM 文件中。但是,您需要添加对 junit 的依赖,如下所示:
<dependencies>
[...]
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
[...]
</dependencies>
surefire 插件不仅与 JUnit 相关联,还可以与其他测试框架一起使用。如果您使用 TestNG,则需要添加对 testng 的依赖,如下所示:
<dependencies>
[...]
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.3.1</version>
<scope>test</scope>
</dependency>
[...]
</dependencies>
surefire 插件引入了一个称为测试提供者的概念。您可以在插件内部指定一个测试提供者;如果没有指定,它将来自依赖 JAR 文件。例如,如果您想使用 junit47 提供者,那么在插件配置中,您可以指定如下。surefire 插件默认支持四个测试提供者:surefire-junit3、surefire-junit4、surefire-junit47 和 surefire-testng:
<plugins>
[...]
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.17</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.surefire</groupId>
<artifactId>surefire-junit47</artifactId>
<version>2.17</version>
</dependency>
</dependencies>
</plugin>
[...]
</plugins>
由于所有 Maven 项目都从父 POM 文件继承 surefire 插件,因此除非绝对必要,否则您不需要在应用程序 POM 文件中覆盖其配置。覆盖父配置的原因之一是覆盖默认测试提供者选择算法。
站点插件
site 插件为 Maven 项目生成静态 HTML 网页内容,包括在项目中配置的报告。这定义了八个目标,其中每个目标在 Maven site 生命周期的四个阶段之一中运行:pre-site、site、post-site 和 site-deploy,可以描述如下:
-
site:site: 此目标为单个 Maven 项目生成站点 -
site:deploy: 此目标通过 Wagon 支持的协议将生成的站点部署到 POM 文件<distributionManagement>部分中指定的站点 URL -
site:run: 此目标使用 Jetty 网络服务器打开站点 -
site:stage: 此目标根据 POM 文件<distributionManagement>部分中指定的站点 URL 在本地预发布或模拟目录中生成站点 -
site:stage-deploy: 此目标将生成的站点部署到 POM 文件<distributionManagement>部分中指定的站点 URL 的预发布或模拟目录 -
site:attach-descriptor: 此目标将站点描述符(site.xml)添加到要安装/部署的文件列表中 -
site:jar: 此目标将站点输出捆绑到一个 JAR 文件中,以便可以部署到仓库 -
site:effective-site: 此目标在继承和插值site.xml之后计算有效站点描述符
所有 Maven 项目都从父 POM 文件继承 site 插件。如下所示配置,父 POM 定义了 site 插件。它将 site 和 deploy 目标与 Maven default 生命周期的 site 和 site-deploy 阶段相关联:
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.3</version>
<executions>
<execution>
<id>default-site</id>
<phase>site</phase>
<goals>
<goal>site</goal>
</goals>
<configuration>
<outputDirectory>
PROJECT_HOME/target/site</outputDirectory>
<reportPlugins>
<reportPlugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>
maven-project-info-reports-plugin
</artifactId>
</reportPlugin>
</reportPlugins>
</configuration>
</execution>
<execution>
<id>default-deploy</id>
<phase>site-deploy</phase>
<goals>
<goal>deploy</goal>
</goals>
<configuration>
<outputDirectory>
PROJECT_HOME/target/site</outputDirectory>
<reportPlugins>
<reportPlugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>
maven-project-info-reports-plugin
</artifactId>
</reportPlugin>
</reportPlugins>
</configuration>
</execution>
</executions>
<configuration>
<outputDirectory>
PROJECT_HOME/target/site</outputDirectory>
<reportPlugins>
<reportPlugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>
maven-project-info-reports-plugin
</artifactId>
</reportPlugin>
</reportPlugins>
</configuration>
</plugin>
如前配置所述,当你运行 mvn site 或 mvn site:site 时,生成的 HTML 网页内容将被创建在项目主目录下的 target/site 目录中。site 插件的 site 目标仅生成 HTML 网页内容;要部署它,你需要使用 deploy 目标。要将 site 部署到远程应用程序服务器,你需要在应用程序 POM 文件的 distributionManagement 部分下指定远程机器的详细信息,如下所示:
<project>
...
<distributionManagement>
<site>
<id>mycompany.com</id>
<url>scp://mycompany/www/docs/project/</url>
</site>
</distributionManagement>
...
</project>
要配置凭证以连接到远程计算机,你需要在 USER_HOME/.m2/settings.xml 的 <servers> 父元素下添加以下 <server> 配置元素:
<server>
<id>mycompany.com</id>
<username>my_username</username>
<password>my_password</password>
</server>
通过执行 Maven site 插件的 deploy 目标,可以将生成的站点或网页内容部署到远程位置,如下所示:
$ mvn site:deploy
在大多数情况下,你不需要覆盖 site 插件配置。
jar 插件
jar 插件可以从你的 Maven 项目中创建一个 JAR 文件。jar 插件的 jar 目标绑定到 Maven default 生命周期的 package 阶段。当你输入 mvn clean install 时,Maven 将执行 default 生命周期中的所有阶段,包括 install 阶段,这也包括 package 阶段。
以下命令显示了如何执行 jar 插件的 jar 目标:
$ mvn jar:jar
所有 Maven 项目都从超级 POM 文件继承了 jar 插件。如下所示配置,超级 POM 定义了 jar 插件。它将 jar 目标与 Maven default 生命周期的 package 阶段关联起来。
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>default-jar</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
在大多数情况下,你不需要覆盖 jar 插件配置,除非你需要创建一个可执行的 JAR 文件。
注意
使用 maven-jar-plugin 创建一个可执行的 JAR 文件,可以在 maven.apache.org/shared/maven-archiver/examples/classpath.html 找到。
source 插件
source 插件创建一个包含项目源代码的 JAR 文件。它定义了五个目标:aggregate、jar、test-jar、jar-no-fork 和 test-jar-no-fork。这五个 source 插件的目标都在 default 生命周期的 package 阶段下运行。
与我们之前讨论的任何插件不同,如果你想使用 Maven default 生命周期执行 source 插件,它必须在项目 POM 文件中定义,如下所示。超级 POM 文件没有定义 source 插件;它必须定义在你的 Maven 项目内部:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.3</version>
<configuration>
<outputDirectory>
/absolute/path/to/the/output/directory
</outputDirectory>
<finalName>filename-of-generated-jar-file</finalName>
<attach>false</attach>
</configuration>
</plugin>
</plugins>
</build>
...
</project>
jar 插件和 source 插件之间的区别是什么?两者都创建 JAR 文件;然而,jar 插件从二进制工件创建 JAR 文件,而 source 插件从源代码创建 JAR 文件。小型开源项目使用这种方法来分发相应的源代码和二进制工件。
resources 插件
resources插件将主项目关联的资源以及测试资源复制到项目输出目录。resources插件的resources目标将主资源复制到主输出目录,并在 Maven default生命周期的process-resources阶段运行。testResources目标将所有与测试关联的资源复制到测试输出目录,并在 Maven default生命周期的process-test-resources阶段运行。copyResources目标可以被配置为将任何资源复制到项目输出目录,并且这不受 Maven default生命周期中任何阶段的限制。
所有 Maven 项目都从父 POM 文件继承了resources插件。如下配置所示,父 POM 定义了resources插件。它将resources和testResources目标与 Maven default生命周期的process-resources和process-test-resources阶段关联。当你输入mvn clean install时,Maven 将执行default生命周期中的所有阶段,包括install阶段,这也包括process-resources和process-test-resources阶段。
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>default-resources</id>
<phase>process-resources</phase>
<goals>
<goal>resources</goal>
</goals>
</execution>
<execution>
<id>default-testResources</id>
<phase>process-test-resources</phase>
<goals>
<goal>testResources</goal>
</goals>
</execution>
</executions>
</plugin>
在大多数情况下,你不需要覆盖resources插件配置,除非你有特定的需求来过滤resources。
注意
关于使用maven-resources-plugin进行资源过滤的更多详细信息,可以在maven.apache.org/plugins/maven-resources-plugin/examples/filter.html找到。
发布插件
发布一个项目需要执行许多重复的任务。Maven release插件的目标是自动化这些任务。release插件定义了以下八个目标,这些目标在两个阶段执行:准备发布和执行发布:
-
release:clean:此目标在发布准备后进行清理 -
release:prepare:此目标在软件配置管理(SCM)中准备发布 -
release:prepare-with-pom:此目标在 SCM 中准备发布,并通过完全解析依赖关系生成发布 POM -
release:rollback:此目标回滚到之前的发布 -
release:perform:此目标执行 SCM 中的发布 -
release:stage:此目标将 SCM 中的发布执行到一个暂存文件夹/仓库中 -
release:branch:此目标创建一个包含所有更新版本的当前项目分支 -
release:update-versions:此目标更新 POM 中的版本
准备阶段将使用release:prepare目标完成以下任务:
-
它验证源代码中的所有更改都已提交。
-
它确保没有 SNAPSHOT 依赖项。在项目开发阶段,我们使用 SNAPSHOT 依赖项;然而,在发布时,所有依赖项都应该更改为每个依赖项的最新发布版本。
-
项目 POM 文件版本将从快照版本更改为具体的版本号。
-
项目 POM 文件中的 SCM 信息将更新,以包含标签的最终目的地。
-
它对修改后的 POM 文件执行所有测试。
-
它将修改后的 POM 文件提交到源代码管理(SCM)并使用版本名称标记代码。
-
它将主分支(trunk)中的 POM 文件版本更改为快照版本,并将修改后的 POM 文件提交到主分支。
最后,将使用 release:perform 目标执行发布。这将从源代码管理(SCM)中的 release 标签检出代码,并运行一系列预定义的目标:site 和 deploy-site。
maven-release-plugin 在父 POM 文件中未定义;它应该在您的应用程序 POM 文件中显式定义。releaseProfiles 配置元素定义了要发布的配置文件,而 goals 配置元素定义了在 release:perform 期间要执行的插件目标,如下所示:
<plugin>
<artifactId>maven-release-plugin</artifactId>
<version>2.5</version>
<configuration>
<releaseProfiles>release</releaseProfiles>
<goals>deploy assembly:single</goals>
</configuration>
</plugin>
插件发现和执行
要将插件与您的 Maven 项目关联,您必须在您的应用程序 POM 文件中显式定义它,或者应该从父 POM 或超级 POM 文件中继承它。让我们看看 Maven 的 jar 插件。jar 插件由超级 POM 文件定义,所有 Maven 项目都继承它。要定义一个插件(不是从 POM 层次继承的),或将插件与您的 Maven 项目关联,您必须在应用程序 pom.xml 的 build/plugins/plugin 元素下添加插件配置。这样,您可以将任意数量的插件与项目关联,如下所示:
<project>
...
<build>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>default-jar</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
在 Maven 执行环境中,重要的是不仅您的应用程序 POM 文件,还有有效 POM 文件。有效 POM 文件是由项目 POM 文件、任何父 POM 文件和超级 POM 文件构建的。
Maven 插件可以通过两种方式执行:
-
使用生命周期
-
直接调用插件目标
如果通过生命周期执行,则与生命周期的不同阶段相关联的插件目标。当每个阶段执行时,所有插件目标也将执行,前提是项目的有效 POM 文件在其插件配置下定义了相应的插件。即使您尝试直接调用插件目标(例如,mvn jar:jar),目标也只有在相应的插件与项目相关联时才会执行。
无论哪种方式,Maven 如何找到与提供的插件目标对应的插件?
与 Maven 中的任何其他依赖项一样,插件也通过三个坐标唯一标识:groupId、artifactId 和 version。然而,对于插件,你不需要显式指定 groupId。Maven 默认假设两个 groupId 元素:org.apache.maven.plugins 和 org.codehaus.mojo。首先,它会尝试从 USER_HOME/.m2/repository/org/apache/maven/plugins 中定位插件,如果失败,则从 USER_HOME/.m2/repository/org/codehaus/mojo 中定位。
在之前的示例插件配置中,你可能找不到 groupId。jar 插件位于 USER_HOME/.m2/repository/org/apache/maven/plugins/maven-jar-plugin。
Maven 还允许你添加自己的插件组,并且它们可以被包含在插件发现中。你可以通过更新 USER_HOME/.m2/settings.xml 或 MAVEN_HOME/conf/settings.xml 来实现,如下所示:
<pluginGroups>
<pluginGroup>com.packt.plugins</pluginGroup>
</pluginGroups>
Maven 将始终优先考虑之前的配置,然后开始寻找已知的 groupId 元素:org.apache.maven.plugins 和 org.codehaus.mojo。
让我们看看一些在流行的开源项目中使用的示例插件配置。
Apache Felix 为 Maven 提供了一个 bundle 插件,该插件可以将 Maven 项目转换成 OSGi 打包。另一个开源项目 WSO2 Carbon 在其开发中使用了这个 bundle 插件。你可以在 svn.wso2.org/repos/wso2/carbon/platform/branches/turing/service-stubs/org.wso2.carbon.qpid.stub/4.2.0/pom.xml 找到示例 POM 文件,该文件使用了插件。这是一个自定义插件,不属于 Maven 默认已知的任何 groupId 元素。在这种情况下,任何使用该插件的人都必须使用 groupId 进行限定,或者他们必须添加前面讨论过的相应 groupId 元素到 pluginGroups 配置元素中。
以下代码展示了 WSO2 Carbon 项目的插件配置:
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-SymbolicName>
${project.artifactId}</Bundle-SymbolicName>
<Bundle-Name>${project.artifactId}</Bundle-Name>
<Carbon-Component>UIBundle</Carbon-Component>
<Import-Package>
org.apache.axis2.*; version="${axis2.osgi.version.range}",
org.apache.axiom.*; version="${axiom.osgi.version.range}",
*;resolution:=optional
</Import-Package>
<Export-Package>
org.wso2.carbon.qpid.stub.*;version="${carbon.platform.package.export.version}",
</Export-Package>
</instructions>
</configuration>
</plugin>
插件管理
如果你仔细查看之前的配置,你不会看到 bundle 插件的版本。这就是 pluginManagement 元素发挥作用的地方。通过 pluginManagement 配置元素,你可以避免重复使用插件版本。一旦在 pluginManagement 下定义了一个插件,所有子 POM 文件都将继承该配置。
WSO2 Carbon 项目在其子项目的 pluginManagement 部分 svn.wso2.org/repos/wso2/carbon/platform/branches/turing/parent/pom.xml 中定义了所有使用的插件,所有项目都继承它。配置的一部分如下:
<pluginManagement>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.5</version>
<extensions>true</extensions>
</plugin>
</pluginManagement>
注意
我们将在 第七章 最佳实践 中详细讨论插件管理。
插件仓库
当 Maven 在其本地仓库中找不到插件时,会按需下载插件。默认情况下,Maven 会在由超级 POM 文件定义的 Maven 插件仓库中查找任何本地不可用的插件(这是默认行为;你也可以在你的应用程序 POM 文件中定义插件仓库)。以下代码片段展示了如何定义插件仓库:
<pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Maven Plugin Repository</name>
<url>http://repo1.maven.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<updatePolicy>never</updatePolicy>
</releases>
</pluginRepository>
</pluginRepositories>
如果你开发了一个自定义插件,就像 Apache Felix 的bundle插件一样,你必须通过插件仓库使其对其他人可用,并且任何其他该插件的消费者,例如 WSO2 Carbon 项目,必须在它的 POM 文件或父 POM 文件中定义相应的插件仓库。
注意
WSO2 Carbon 项目在其父 POM 文件中定义了两个插件仓库,位于svn.wso2.org/repos/wso2/carbon/platform/branches/turing/parent/pom.xml。
Apache Felix bundle插件可在dist.wso2.org/maven2/org/apache/felix/maven-bundle-plugin/找到。
以下配置是 WSO2 Carbon 项目parent/pom.xml的一部分,它定义了两个插件仓库:
<pluginRepositories>
<pluginRepository>
<id>wso2-maven2-repository-1</id>
<url>http://dist.wso2.org/maven2</url>
</pluginRepository>
<pluginRepository>
<id>wso2-maven2-repository-2</id>
<url>http://dist.wso2.org/snapshots/maven2</url>
</pluginRepository>
</pluginRepositories>
插件作为扩展
如果你查看 Apache Felix bundle插件的定义,你可能已经注意到了extensions配置元素,它被设置为true:
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
</plugin>
如我们之前讨论的,bundle插件的目标是从 Maven 项目构建一个 OSGi 包。换句话说,Apache Felix bundle插件引入了一个新的打包类型,具有现有的文件扩展名.jar。如果你查看消费bundle插件的 WSO2 Carbon 项目的 POM 文件,你可以看到项目的打包被设置为bundle(svn.wso2.org/repos/wso2/carbon/platform/branches/turing/service-stubs/org.wso2.carbon.qpid.stub/4.2.0/pom.xml),如下所示:
<packaging>bundle</packaging>
如果你将一个插件与你的项目关联,该插件引入了新的打包类型或自定义的生命周期,那么你必须将extensions配置元素的值设置为true。一旦这样做,Maven 引擎将进一步查找对应jar插件的META-INF/plexus目录中的components.xml文件。
摘要
在本章中,我们专注于 Maven 插件。Maven 只提供了一个构建框架,而 Maven 插件执行实际的任务。Maven 有一套庞大且丰富的插件,你需要编写自己的自定义插件的可能性非常小。本章涵盖了最常用的 Maven 插件,并稍后解释了插件是如何被发现和执行的。如果你想了解自定义插件开发,请参阅由 Packt Publishing 出版的《Mastering Apache Maven 3》一书。
在下一章中,我们将重点关注 Maven 构建生命周期。
第五章。构建生命周期
Maven 构建生命周期由一系列定义良好的阶段组成。每个阶段由 Maven 插件定义的一组目标组成,生命周期定义了执行顺序。Maven 插件是一组目标,其中每个目标都负责执行特定的操作。我们在第四章“Maven 插件”中详细讨论了 Maven 插件。第四章。
在本章中,将涵盖以下主题:
-
Maven 中的标准生命周期
-
生命周期绑定
-
构建自定义生命周期扩展
下图显示了 Maven 插件目标和生命周期阶段之间的关系:

让我们来看一下每个 Java 开发者都熟悉的简单 Maven 构建命令:
$ mvn clean install
这会做什么?作为一个开发者,你执行过这个命令多少次?你是否想过它内部发生了什么?如果没有,现在是时候探索它了。
Maven 中的标准生命周期
Maven 随带三个标准生命周期:
-
clean -
default -
site
每个生命周期定义了自己的阶段集。
清洁生命周期
clean 生命周期定义了三个阶段:pre-clean、clean 和 post-clean。生命周期中的一个阶段只是构建执行路径中的一个有序占位符。例如,clean 生命周期中的 clean 阶段本身不能做任何事情。在 Maven 架构中,它有两个关键元素:名词和动词。与特定项目相关的名词和动词都在 POM 文件中定义。项目的名称、父项目的名称、依赖项和打包类型都是名词。插件将动词引入 Maven 构建系统,并通过其目标定义在构建执行期间需要执行的操作。一个插件是一组目标。插件中的每个目标都可以单独执行,也可以注册为 Maven 构建生命周期中某个阶段的一部分。
当你输入 mvn clean 时,它会执行 clean 生命周期中定义的所有阶段,包括 clean 阶段。不要混淆;在这个命令中,clean 不是生命周期的名称,而是阶段的名称。它只是一个巧合,阶段的名字恰好与生命周期的名字相同。在 Maven 中,你不能简单地通过名称执行生命周期——它必须是阶段的名称。Maven 将找到相应的生命周期,并将执行其中直到给定阶段的所有阶段(包括该阶段)。
当你输入 mvn clean 时,它会清理项目的工作目录(默认情况下,它是 target 目录)。这是通过 Maven 的 clean 插件完成的。要了解更多关于 Maven clean 插件的信息,请输入以下命令。它描述了 clean 插件内部定义的所有目标:
$ mvn help:describe -Dplugin=clean
Name: Maven Clean Plugin
Description: The Maven Clean Plugin is a plugin that removes files generated at build-time in a project's directory.
Group Id: org.apache.maven.plugins
Artifact Id: maven-clean-plugin
Version: 2.5
Goal Prefix: clean
This plugin has 2 goals.
clean:clean
Description: Goal, which cleans the build. This attempts to clean a project's working directory of the files that were generated at build-time. By default, it discovers and deletes the directories configured in project.build.directory, project.build.outputDirectory, project.build.testOutputDirectory, andproject.reporting.outputDirectory.Files outside the default may also be included in the deletion by configuring the filesets tag.
clean:help
Description: Display help information on maven-clean-plugin.Call
mvn clean:help -Ddetail=true -Dgoal=<goal-name> to display parameter details.
For more information, run 'mvn help:describe [...] -Ddetail'
注意
Maven 中的每件事都是一个插件。甚至我们之前执行以获取 clean 插件目标详情的命令也执行了另一个插件——help 插件。以下命令将描述 help 插件:
$ mvn help:describe -Dplugin=help
describe 是在 help 插件内部定义的目标。
clean 插件中定义了两个目标:clean 和 help。如前所述,插件中的每个目标都可以单独执行,也可以注册为 Maven 构建生命周期中某个阶段的组成部分。clean 插件的 clean 目标可以单独使用以下命令执行:
$ mvn clean:clean
下图显示了 Maven clean 插件目标与 clean 生命周期阶段之间的关系:

在上一个命令中 clean 这个词的第一个实例是 clean 插件的限定符,而第二个实例是目标名称。当您输入 mvn clean 时,执行的是相同的 clean 目标。然而,这次它是通过 clean 生命周期的 clean 阶段执行的,并且它还会执行相应生命周期中直到并包括 clean 阶段的所有阶段——不仅仅是 clean 阶段。clean 插件的 clean 目标默认配置为在 clean 生命周期的 clean 阶段执行。可以通过应用程序 POM 文件提供插件目标到生命周期阶段的映射;如果没有提供,它将继承自父 POM 文件。默认定义 clean 插件的父 POM 文件将该插件添加到 clean 生命周期的 clean 阶段。您不能在不同的生命周期中定义具有相同名称的阶段。
下面的代码片段显示了 Maven clean 插件的 clean 目标是如何与 clean 生命周期阶段的 clean 阶段关联的:
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>2.5</version>
<executions>
<execution>
<id>default-clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
clean 生命周期的 pre-clean 和 post-clean 阶段没有任何插件绑定。pre-clean 阶段的目标是在清理任务之前执行任何操作,而 post-clean 阶段的目标是在清理任务之后执行任何操作。如果您需要将任何插件与这两个阶段关联,只需将它们添加到相应的插件配置中。
默认生命周期
Maven 的 default 生命周期定义了 23 个阶段。当您运行 mvn clean install 命令时,它将执行 default 生命周期中直到并包括 install 阶段的所有阶段。更准确地说,Maven 首先执行 clean 生命周期中直到并包括 clean 阶段的所有阶段,然后执行 default 生命周期中直到并包括 install 阶段。
default 生命周期中的阶段没有关联的插件目标。每个阶段的插件绑定由相应的打包类型(即 jar 或 war)定义。如果您的 Maven 项目的打包类型是 jar,那么它将为每个阶段定义自己的插件集。如果打包类型是 war,那么它将有自己的插件集。以下要点总结了在 default 生命周期下按执行顺序定义的所有阶段:
-
validate:此阶段验证项目 POM 文件,并确保执行构建所需的所有相关信息都可用。 -
initialize:此阶段通过设置正确的目录结构和初始化属性来初始化构建。 -
generate-sources:此阶段生成所需的任何源代码。 -
process-sources:此阶段处理生成的源代码;例如,在这个阶段可以运行一个插件,根据一些定义的准则过滤源代码。 -
generate-resources:此阶段生成需要与最终工件一起打包的任何资源。 -
process-resources:此阶段处理生成的资源。它将资源复制到目标目录,并使它们准备好进行打包。 -
compile:此阶段编译源代码。 -
process-classes:此阶段可以在compile阶段之后执行任何字节码增强。 -
generate-test-sources:此阶段生成测试所需的源代码。 -
process-test-sources:此阶段处理生成的测试源代码;例如,在这个阶段可以运行一个插件,根据一些定义的准则过滤源代码。 -
generate-test-resources:此阶段生成运行测试所需的所有资源。 -
process-test-resources:此阶段处理生成的测试资源。它将资源复制到目标目录,并使它们准备好进行测试。 -
test-compile:此阶段编译测试源代码。 -
process-test-classes:此阶段可以在test-compile阶段之后执行任何字节码增强。 -
test:此阶段使用适当的单元测试框架执行测试。 -
prepare-package:此阶段在组织要打包的工件时很有用。 -
package:此阶段将工件打包成可分发格式,例如,JAR 或 WAR。 -
pre-integration-test:此阶段执行在运行集成测试之前所需的操作(如果有)。这可能用于启动任何外部应用程序服务器并将工件部署到不同的测试环境中。 -
integration-test:此阶段运行集成测试。 -
post-integration-test:此阶段可以在运行集成测试后执行任何清理任务。 -
verify:此阶段验证包的有效性。检查有效性的标准需要由相应的插件定义。 -
install:此阶段将最终工件安装到本地仓库。 -
deploy:此阶段将最终工件部署到远程仓库。
注意
给定 Maven 项目的打包类型在 pom.xml 文件的 <packaging> 元素下定义。如果省略此元素,则 Maven 假设它为 jar 打包。
下图显示了在 Maven default 生命周期下定义的所有阶段及其执行顺序:

注意
更多关于 Maven 生命周期的详细信息可以在maven.apache.org/ref/3.3.3/maven-core/lifecycles.html找到。
让我们看看一个具体的例子。运行以下命令针对一个具有 jar 打包方式的 Maven 项目:
$ mvn help:describe -Dcmd=deploy
注意
如果您没有这样的项目,您可以从svn.wso2.org/repos/wso2/people/prabath/maven-mini/chapter05/jose/下载一个示例 Maven 项目。
这里,我们使用 Maven help 插件来查找与 jar 打包方式对应的 deploy 阶段的更多详细信息,它将产生以下输出:
It is a part of the lifecycle for the POM packaging 'jar'. This lifecycle includes the following phases:
* validate: Not defined
* initialize: Not defined
* generate-sources: Not defined
* process-sources: Not defined
* generate-resources: Not defined
* process-resources: org.apache.maven.plugins:maven-resources-plugin:2.6:resources
* compile: org.apache.maven.plugins:maven-compiler-plugin:2.5.1:compile
* process-classes: Not defined
* generate-test-sources: Not defined
* process-test-sources: Not defined
* generate-test-resources: Not defined
* process-test-resources: org.apache.maven.plugins:maven-resources-plugin:2.6:testResources
* test-compile: org.apache.maven.plugins:maven-compiler-plugin:2.5.1:testCompile
* process-test-classes: Not defined
* test: org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test
* prepare-package: Not defined
* package: org.apache.maven.plugins:maven-jar-plugin:2.4:jar
* pre-integration-test: Not defined
* integration-test: Not defined
* post-integration-test: Not defined
* verify: Not defined
* install: org.apache.maven.plugins:maven-install-plugin:2.4:install
* deploy: org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
输出列出了针对 default 生命周期不同阶段的 jar 打包方式注册的所有 Maven 插件。maven-jar-plugin 的 jar 目标注册在 package 阶段,而 maven-install-plugin 的 install 目标注册在 install 阶段。
让我们运行之前的命令针对一个具有 war 打包方式的 POM 文件。它产生了以下输出:
It is a part of the lifecycle for the POM packaging 'war'. This life includes the following phases:
* validate: Not defined
* initialize: Not defined
* generate-sources: Not defined
* process-sources: Not defined
* generate-resources: Not defined
* process-resources: org.apache.maven.plugins:maven-resources-plugin:2.6:resources
* compile: org.apache.maven.plugins:maven-compiler-plugin:2.5.1:compile
* process-classes: Not defined
* generate-test-sources: Not defined
* process-test-sources: Not defined
* generate-test-resources: Not defined
* process-test-resources: org.apache.maven.plugins:maven-resources-plugin:2.6:testResources
* test-compile: org.apache.maven.plugins:maven-compiler-plugin:2.5.1:testCompile
* process-test-classes: Not defined
* test: org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test
* prepare-package: Not defined
* package: org.apache.maven.plugins:maven-war-plugin:2.2:war
* pre-integration-test: Not defined
* integration-test: Not defined
* post-integration-test: Not defined
* verify: Not defined
* install: org.apache.maven.plugins:maven-install-plugin:2.4:install
* deploy: org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
现在,如果您查看 package 阶段,您会注意到我们有一个不同的插件目标:maven-war-plugin 的 war 目标。
与 jar 和 war 打包方式类似,其他打包类型为 default 生命周期定义了自己的绑定。
站点生命周期
site 生命周期定义了四个阶段:pre-site、site、post-site 和 site-deploy。没有 Maven site 插件,site 生命周期就没有价值。site 插件用于为项目生成静态 HTML 内容。生成的 HTML 内容还将包括与项目相对应的适当报告。site 插件定义了八个目标,其中两个直接与 site 生命周期中的阶段相关联。
让我们运行以下命令针对一个 POM 文件来描述站点目标:
$ mvn help:describe -Dcmd=site
如以下输出所示,site 插件的 site 目标与 site 阶段相关联,而 site 插件的 deploy 目标与 site-deploy 阶段相关联:
[INFO] 'site' is a lifecycle with the following phases:
* pre-site: Not defined
* site: org.apache.maven.plugins:maven-site-plugin:3.3:site
* post-site: Not defined
* site-deploy: org.apache.maven.plugins:maven-site-plugin:3.3:deploy
以下图显示了 Maven site 插件目标和 site 生命周期阶段之间的关系:

生命周期绑定
在讨论 default 生命周期时,我们简要提到了生命周期绑定的概念。default 生命周期定义时没有关联的生命周期绑定,而 clean 和 site 生命周期则定义了绑定。标准的 Maven 生命周期及其关联的绑定定义在 MAVEN_HOME/lib/maven-core-3.3.3.jar 文件夹下的 META-INF/plex/components.xml 文件中。
这是 default 生命周期没有关联插件绑定的配置:
<component>
<role>org.apache.maven.lifecycle.Lifecycle</role>
<implementation>
org.apache.maven.lifecycle.Lifecycle
</implementation>
<role-hint>default</role-hint>
<configuration>
<id>default</id>
<phases>
<phase>validate</phase>
<phase>initialize</phase>
<phase>generate-sources</phase>
<phase>process-sources</phase>
<phase>generate-resources</phase>
<phase>process-resources</phase>
<phase>compile</phase>
<phase>process-classes</phase>
<phase>generate-test-sources</phase>
<phase>process-test-sources</phase>
<phase>generate-test-resources</phase>
<phase>process-test-resources</phase>
<phase>test-compile</phase>
<phase>process-test-classes</phase>
<phase>test</phase>
<phase>prepare-package</phase>
<phase>package</phase>
<phase>pre-integration-test</phase>
<phase>integration-test</phase>
<phase>post-integration-test</phase>
<phase>verify</phase>
<phase>install</phase>
<phase>deploy</phase>
</phases>
</configuration>
</component>
components.xml文件,也称为组件描述符,描述了 Maven 管理 Maven 项目生命周期所需的所有属性。role元素指定了由此生命周期组件公开的 Java 接口并定义了组件的类型。所有生命周期组件都必须以org.apache.maven.lifecycle.Lifecycle作为角色。implementation标签指定了接口的具体实现。组件的身份由角色和role-hint元素的组合定义。role-hint元素不是必需的元素;然而,如果我们有多个相同类型的元素,那么我们必须定义一个role-hint元素。对应于 Maven 生命周期,生命周期的名称被设置为role-hint元素的值。
注意
Maven 使用components.xml来定义比 Maven 生命周期更多的其他组件。根据组件的类型,设置role元素的值。
clean生命周期是通过与maven-clean-plugin的clean目标关联的插件绑定来定义的。插件绑定定义在default-phases元素下。配置如下:
<component>
<role>org.apache.maven.lifecycle.Lifecycle</role>
<implementation>
org.apache.maven.lifecycle.Lifecycle
</implementation>
<role-hint>clean</role-hint>
<configuration>
<id>clean</id>
<phases>
<phase>pre-clean</phase>
<phase>clean</phase>
<phase>post-clean</phase>
</phases>
<default-phases>
<clean>
org.apache.maven.plugins:maven-clean-plugin:2.4.1:clean
</clean>
</default-phases>
</configuration>
</component>
Maven 的site生命周期是通过与maven-site-plugin的site和site-deploy目标关联的插件绑定来定义的。插件绑定定义在default-phases元素下,配置如下:
<component>
<role>org.apache.maven.lifecycle.Lifecycle</role>
<implementation>
org.apache.maven.lifecycle.Lifecycle
</implementation>
<role-hint>site</role-hint>
<configuration>
<id>site</id>
<phases>
<phase>pre-site</phase>
<phase>site</phase>
<phase>post-site</phase>
<phase>site-deploy</phase>
</phases>
<default-phases>
<site>
org.apache.maven.plugins:maven-site-plugin:2.0.1:site
</site>
<site-deploy>
org.apache.maven.plugins:maven-site-plugin:2.0.1:deploy
</site-deploy>
</default-phases>
</configuration>
</component>
最后,让我们看看如何定义default生命周期的jar插件绑定。以下component元素定义了一个插件绑定到现有的生命周期。相关生命周期定义在configuration/lifecycles/lifecycle/id元素下:
<component>
<role>
org.apache.maven.lifecycle.mapping.LifecycleMapping
</role>
<role-hint>jar</role-hint>
<implementation>org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping
</implementation>
<configuration>
<lifecycles>
<lifecycle>
<id>default</id>
<phases>
<process-resources>
org.apache.maven.plugins:maven-resources-plugin:2.4.3:resources
</process-resources>
<compile>
org.apache.maven.plugins:maven-compiler-plugin:2.3.2:compile
</compile>
<process-test-resources>
org.apache.maven.plugins:maven-resources-plugin:2.4.3:testResources
</process-test-resources>
<test-compile>
org.apache.maven.plugins:maven-compiler-plugin:2.3.2:testCompile
</test-compile>
<test>
org.apache.maven.plugins:maven-surefire-plugin:2.5:test
</test>
<package>
org.apache.maven.plugins:maven-jar-plugin:2.3.1:jar
</package>
<install>
org.apache.maven.plugins:maven-install-plugin:2.3.1:install
</install>
<deploy>
org.apache.maven.plugins:maven-deploy-plugin:2.5:deploy
</deploy>
</phases>
</lifecycle>
</lifecycles>
</configuration>
</component>
生命周期扩展
Maven 的生命周期扩展允许你自定义标准的构建行为。让我们看看org.apache.maven.AbstractMavenLifecycleParticipant类。一个自定义的生命周期扩展应该从AbstractMavenLifecycleParticipant类扩展,该类提供了以下三个你可以重写的方法:
-
afterProjectsRead(MavenSession session): 此方法在所有 Maven 项目实例创建后调用。对于每个 POM 文件,将有一个项目实例。在一个大规模的构建系统中,你有一个父 POM,它指向多个子 POM 文件。 -
afterSessionEnd(MavenSession session): 此方法在所有 Maven 项目构建完成后被调用。 -
afterSessionStart(MavenSession session): 此方法在创建MavenSession实例后调用。
让我们尝试以下示例:
package com.packt.lifecycle.ext;
import org.apache.maven.AbstractMavenLifecycleParticipant;
import org.apache.maven.MavenExecutionException;
import org.apache.maven.execution.MavenSession;
import org.codehaus.plexus.component.annotations.Component;
@Component(role = AbstractMavenLifecycleParticipant.class, hint ="packt")
public class PACKTLifeCycleExtension extends AbstractMavenLifecycleParticipant {
@Override
public void afterProjectsRead(MavenSession session) {
System.out.println("All Maven project instances are created.");
System.out.println("Offline building: " + session.isOffline());
}
@Override
public void afterSessionEnd(MavenSession session)
throws MavenExecutionException {
System.out.println("All Maven projects are built.");
}
}
以下应用 POM 文件可以构建前面的代码:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>com.packt.lifecycle.ext</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-compat</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-component-metadata</artifactId>
<version>1.5.5</version>
<executions>
<execution>
<goals>
<goal>generate-metadata</goal>
<goal>generate-test-metadata</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
在此 POM 文件中,我们使用plexus-component-metadata插件从源标签和类注解生成 Plexus 描述符。
一旦使用 mvn clean install 成功构建了扩展项目,我们就需要将这个扩展集成到其他 Maven 构建中。你可以通过两种方式来完成;一种是将它作为扩展添加到项目的 POM 文件中,如下面的代码所示:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>
com.packt.lifecycle.ext.sample.project
</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>Custom Lifecycle Extension Project</name>
<build>
<extensions>
<extension>
<groupId>com.packt</groupId>
<artifactId>com.packt.lifecycle.ext</artifactId>
<version>1.0.0</version>
</extension>
</extensions>
</build>
</project>
现在,你可以使用 mvn clean install 构建示例项目。它将产生以下输出:
[INFO] Scanning for projects...
All Maven project instances are created.
Offline building: false
[INFO] --------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] --------------------------------------------------------------
[INFO] Total time: 1.328 s
[INFO] Finished at: 2014-07-29T11:29:52+05:30
[INFO] Final Memory: 6M/81M
[INFO] --------------------------------------------------------------
All Maven projects are built.
如果你想要在不更改每个 POM 文件的情况下,为所有 Maven 项目执行这个扩展,那么你需要将生命周期扩展 JAR 文件添加到 MAVEN_HOME/lib/ext。
注意
与生命周期扩展项目对应的完整源代码可以从 svn.wso2.org/repos/wso2/people/prabath/maven-mini/chapter05/ 下载。
摘要
在本章中,我们专注于 Maven 生命周期,并解释了三个标准生命周期是如何工作的,以及我们如何自定义它们。在章节的后面部分,我们讨论了如何开发我们自己的生命周期扩展。
在下一章中,我们将讨论如何构建 Maven 集成。
第六章:Maven Assemblies
Maven 通过插件和生命周期提供了一种可扩展的架构。插件和关联的生命周期支持诸如 .jar、.war、.ear 等多种存档类型。JAR 插件根据 JAR 规范创建一个具有 .jar 扩展名和相关元数据的工件。实际上,JAR 文件是一个包含可选 META-INF 目录的 ZIP 文件。你可以从 docs.oracle.com/javase/7/docs/technotes/guides/jar/jar.html 找到更多关于 JAR 规范的详细信息。
JAR 文件将一组类文件聚合起来构建一个单一的发行单元。WAR 文件将一组 JAR 文件、Java 类、JSP、图片以及许多其他资源聚合到一个单一的发行单元中,该单元可以在 Java EE 应用服务器中部署。然而,当你构建一个产品时,你可能需要将来自不同地方的许多 JAR 文件、WAR 文件、README 文件、LICENSE 文件等聚合到一个单一的 ZIP 文件中。为了构建这样的存档,我们可以使用 Maven assembly 插件。
下图显示了 Maven assembly 的可能内容:

在本章中,我们将讨论以下主题:
-
Maven
assembly插件 -
存档描述符
-
艺术品/资源过滤器
-
构建定制发行存档的端到端示例
Maven assembly 插件生成一个符合用户定义布局的定制存档。这个定制存档也被称为 Maven assembly。换句话说,一个 Maven assembly 是一个根据定制布局构建的发行单元。
assembly 插件
让我们快速看一下一个使用 assembly 插件的真实世界示例。
WSO2 身份服务器(WSO2 IS)是一个开源的身份和权限管理产品,以 ZIP 文件的形式分发,遵循 Apache 2.0 许可证。ZIP 分发是通过 Maven assembly 插件组装的。让我们看一下 WSO2 IS 的 distribution 模块的根 POM 文件,它构建了身份服务器发行版,可在 svn.wso2.org/repos/wso2/carbon/platform/branches/turing/products/is/5.0.0/modules/distribution/pom.xml 找到。
首先,注意 POM 文件的 plugins 部分。在这里,你可以看到 maven-assembly-plugin 与项目相关联。在插件配置内部,你可以使用 execution 元素定义任意数量的执行,该元素是 executions 元素的一个子元素,其配置如下:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>copy_components</id>
<phase>test</phase>
<goals>
<goal>attached</goal>
</goals>
<configuration>
<filters>
<filter>
${basedir}/src/assembly/filter.properties
</filter>
</filters>
<descriptors>
<descriptor>src/assembly/dist.xml</descriptor>
</descriptors>
</configuration>
</execution>
<execution>
<id>dist</id>
<phase>package</phase>
<goals>
<goal>attached</goal>
</goals>
<configuration>
<filters>
<filter>
${basedir}/src/assembly/filter.properties
</filter>
</filters>
<descriptors>
<descriptor>src/assembly/bin.xml</descriptor>
<descriptor>src/assembly/src.xml</descriptor>
<descriptor>src/assembly/docs.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
如果你查看第一个 execution 元素,它将 assembly 插件的 attached 目标与 default 生命周期的 test 阶段关联起来。同样地,第二个 execution 元素将 attached 目标与 default 生命周期的 package 阶段关联起来。
注意
Maven default生命周期包括:validate、initialize、generate-sources、process-sources、generate-resources、process-resources、compile、process-classes、generate-test-sources、process-test-sources、generate-test-resources、process-test-resources、test-compile、process-test-classes、test、prepare-package、package、pre-integration-test、integration-test、post-integration-test、verify、install、deploy。
configuration元素内的所有内容都是插件特定的。在这种情况下,Maven assembly插件知道如何处理filters和descriptors元素。
在这个特定的例子中,只使用了assembly插件的attached目标。assembly插件引入了八个目标;然而,其中六个已经弃用,包括attached目标。不建议使用任何已弃用的目标。稍后,我们将看到如何使用assembly插件的single目标来替代已弃用的attached目标。以下列出了assembly插件的六个已弃用目标。如果你正在使用其中任何一个,你应该将你的项目迁移到使用single目标,除了第四个,即unpack目标。为此,你需要使用 Maven dependency插件的unpack目标。以下列出了assembly插件的六个已弃用目标:
-
assembly:assembly -
assembly:attached -
assembly:directory -
assembly:unpack -
assembly:directory-single -
assembly:directory-inline
注意
关于 Maven assembly插件的更多详细信息及其目标可以在maven.apache.org/plugins/maven-assembly-plugin/plugin-info.html找到。
组件描述符
assembly描述符是一个基于 XML 的配置,它定义了如何构建一个组件以及其内容应该如何结构化。
如果我们回到之前的例子,assembly插件的attached目标会根据assembly描述符创建一个二进制分发,在default Maven 生命周期的test和package阶段。每个阶段的assembly描述符可以在descriptors元素下指定。正如这个特定例子的情况一样,在descriptors父元素下定义了多个descriptor元素。对于package阶段,它有三个组件描述符,如下面的配置所示:
<descriptors>
<descriptor>src/assembly/bin.xml</descriptor>
<descriptor>src/assembly/src.xml</descriptor>
<descriptor>src/assembly/docs.xml</descriptor>
</descriptors>
每个descriptor元素指示assembly插件从哪里加载描述符,并且每个descriptor文件将按照定义的顺序依次执行。
让我们来看看src/assembly/bin.xml文件,它在这里显示:
<assembly>
<formats>
<format>zip</format>
</formats>
注意
描述符元素中的文件路径是相对于distribution模块下的根 POM 文件给出的。你可以在svn.wso2.org/repos/wso2/carbon/platform/branches/turing/products/is/5.0.0/modules/distribution/src/assembly/bin.xml找到完整的bin.xml文件。
format元素的值指定了要生成的存档的最终类型。它可以是指定类型为zip、tar、tar.gz、tar.bz2、jar、dir或war。你可以使用相同的汇编描述符来创建多个格式。在这种情况下,你需要在formats父元素下包含多个format元素。
注意
即使你可以在assembly描述符中指定汇编的格式,但建议通过插件配置来完成。在插件配置中,你可以为你的汇编定义不同的格式,如下面的代码块所示。这里的优点是你可以拥有多个 Maven 配置文件来构建不同的存档类型。我们将在第七章最佳实践中讨论 Maven 配置文件:
<plugin>
<executions>
<execution>
<configuration>
<formats>
<format>zip</format>
</formats>
</configuration>
</execution>
</executions>
</plugin>
<includeBaseDirectory>false</includeBaseDirectory>
当includeBaseDirectory元素的值设置为false时,将不会创建基础目录,如果设置为true,这是默认值,则将在基础目录下创建存档。你可以在baseDirectory元素下指定基础目录的值。在大多数情况下,includeBaseDirectory的值设置为false,以便最终的分发单元直接将所有存档打包在其下方,而不需要另一个根目录:
<fileSets>
<fileSet>
<directory>target/wso2carbon-core-4.2.0</directory>
<outputDirectory>wso2is-${pom.version}</outputDirectory>
<excludes>
<exclude>**/*.sh</exclude>
在fileSets父元素下的每个fileSet元素指定了要组装以构建最终存档的文件集。第一个fileSet元素指示将directory(即target/wso2carbon-core-4.2.0)中的所有内容复制到outputDirectory,排除每个exclude元素下定义的所有文件。如果没有定义排除项,则将directory内部的所有内容复制到outputDirectory。在这个特定的情况下,${pom.version}的值将被在distribution模块下的pom.xml文件中定义的存档version值所替换。
第一个exclude元素指示不要从target/wso2carbon-core-4.2.0内部的任何位置复制任何扩展名为.sh的文件到outputDirectory元素:
<exclude>**/wso2server.bat</exclude>
<exclude>**/axis2services/sample01.aar</exclude>
第二个exclude元素指示不要从target/wso2carbon-core-4.2.0内部的任何位置复制任何名为wso2server.bat的文件到outputDirectory。
第三个exclude元素指示不要从target/wso2carbon-core-4.2.0内部的任何位置复制文件axis2services/sample01.aar到outputDirectory:
<exclude>**/axis2services/Echo.aar</exclude>
<exclude>**/axis2services/Version.aar</exclude>
<exclude>**/pom.xml</exclude>
<exclude>**/version.txt</exclude>
<exclude>**/README*</exclude>
<exclude>**/carbon.xml</exclude>
<exclude>**/axis2/*</exclude>
<exclude>**/LICENSE.txt</exclude>
<exclude>**/INSTALL.txt</exclude>
<exclude>**/release-notes.html</exclude>
<exclude>**/claim-config.xml</exclude>
<exclude>**/log4j.properties</exclude>
<exclude>**/registry.xml</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>
../p2-profile-gen/target/wso2carbon-core-4.2.0/repository/conf/identity
</directory>
<outputDirectory>wso2is-${pom.version}/repository/conf/identity
</outputDirectory>
<includes>
<include>**/*.xml</include>
</includes>
include 元素指示仅从 ../p2-profile-gen/target/wso2carbon-core-4.2.0/repository/conf/identity 目录内的任何位置复制具有 .xml 扩展名的文件到 outputDirectory。如果没有定义 include 元素,则所有内容都将被包含:
</fileSet>
<fileSet>
<directory>
../p2-profile-gen/target/wso2carbon-core-4.2.0/repository/resources/security/ldif
</directory>
<outputDirectory>wso2is-${pom.version}/repository/resources/security/ldif
</outputDirectory>
<includes>
<include>identityPerson.ldif</include>
<include>scimPerson.ldif</include>
<include>wso2Person.ldif</include>
</includes>
前述代码中提到的三个 include 元素指示仅从 ../p2-profile-gen/target/wso2carbon-core/4.2.0/repository/resources/security/ldif 目录内的任何位置复制具有特定名称的文件到 outputDirectory:
</fileSet>
<fileSet>
<directory>
../p2-profile-gen/target/wso2carbon-core-4.2.0/repository/deployment/server/webapps
</directory>
<outputDirectory>${pom.artifactId}-${pom.version}/repository/deployment/server/webapps
</outputDirectory>
<includes>
<include>oauth2.war</include>
</includes>
include 元素指示仅从 ../p2-profile-gen/target/wso2carbon-core/4.2.0/repository/resources/deployment/server/webappas 目录内的任何位置复制名为 oauth2.war 的 WAR 文件到 outputDirectory:
</fileSet>
<fileSet>
<directory>
../p2-profile-gen/target/wso2carbon-core-4.2.0/repository/deployment/server/webapps
</directory>
<outputDirectory>${pom.artifactId}-${pom.version}/repository/deployment/server/webapps
</outputDirectory>
<includes>
<include>authenticationendpoint.war</include>
</includes>
</fileSet>
<fileSet>
<directory>
../styles/service/src/main/resources/web/styles/css
</directory>
<outputDirectory>${pom.artifactId}-${pom.version}/resources/allthemes/Default/admin
</outputDirectory>
<includes>
<include>**/**.css</include>
</includes>
include 元素指示从 ../styles/service/src/main/resources/web/styles/css 目录内的任何位置复制任何具有 .css 扩展名的文件到 outputDirectory:
</fileSet>
<fileSet>
<directory>
../p2-profile-gen/target/WSO2-CARBON-PATCH-4.2.0-0006
</directory>
<outputDirectory>
wso2is-${pom.version}/repository/components/patches/
</outputDirectory>
<includes>
<include>**/patch0006/*.*</include>
</includes>
include 元素指示从 ../p2-profile-gen/target/WSO2-CARBON-PATCH-4.2.0-0006 目录内的任何位置复制 patch006 目录中的所有文件到 outputDirectory:
</fileSet>
</fileSets>
<files>
files 元素在关键功能方面与 fileSets 元素非常相似。两者都可以用来控制组合的内容。
小贴士
当你完全清楚确切源文件位置时,应使用 files/file 元素,而 fileSets/fileSet 元素在根据定义的模式从源中选择文件方面则更加灵活。
下面的片段中的 fileMode 元素定义了一组要附加到复制文件的权限。权限按照四位八进制表示法定义。你可以从 en.wikipedia.org/wiki/File_system_permissions#Octal_notation_and_additional_permissions 了解更多关于四位八进制表示法的信息:
<file>
<source>../p2-profile-gen/target/WSO2-CARBON-PATCH-${carbon.kernel.version}-0006/lib/org.wso2.ciphertool-1.0.0-wso2v2.jar
</source>
<outputDirectory>
${pom.artifactId}-${pom.version}/lib/
</outputDirectory>
<filtered>true</filtered>
<fileMode>644</fileMode>
</file>
<files>
</assembly>
在 assembly 插件的 package 阶段下定义了三个 descriptor 元素。我们之前讨论的那个将创建二进制发行版,而 src/assembly/src.xml 和 src/assembly/docs.xml 文件将分别创建源发行版和文档发行版。
让我们也看看为 test 阶段定义的 assembly 描述符:
<descriptors>
<descriptor>src/assembly/dist.xml</descriptor>
</descriptors>
这相当简短,仅包括构建 WSO2 身份服务器初始分发的所需配置。尽管此项目在test阶段执行此操作,但它似乎没有价值。在这种情况下,似乎需要maven-antrun-plugin,它与package阶段相关联,但在定义assembly插件之前,需要 ZIP 文件分发。理想情况下,除非有非常充分的理由,否则不应在test阶段运行assembly插件。您可能需要准备好的分发来运行集成测试;然而,集成测试应在package阶段之后的integration-test阶段执行。在大多数情况下,assembly插件与 Maven 的default生命周期的package阶段相关联。
以下代码显示了为test阶段在src/assembly/dist.xml中定义的 assembly 描述符:
<assembly>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<!-- Copying p2 profile and osgi bundles-->
<fileSet>
<directory>
../p2-profile-gen/target/wso2carbon-core-4.2.0/repository/components
</directory>
<outputDirectory>wso2is-${pom.version}/repository/components
</outputDirectory>
<excludes>
<exclude>**/eclipse.ini</exclude>
<exclude>**/*.lock</exclude>
<exclude>**/.data</exclude>
<exclude>**/.settings</exclude>
</excludes>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>
wso2is-${pom.version}/repository/deployment/client/modules
</outputDirectory>
<includes>
<include>org.apache.rampart:rampart:mar</include>
</includes>
</dependencySet>
</dependencySets>
</assembly>
此配置引入了一个我们之前未曾见过的元素,即dependencySet。dependencySet元素允许您将项目依赖项包含或排除在构建的最终组装中。在先前的示例中,它将rampart模块添加到outputDirectory元素中。include元素的值应采用以下格式:groupdId:artifactId:type[:classifier][:version]。Maven 将首先在其本地 Maven 仓库中查找具有定义坐标的此工件,如果找到,它将将其复制到outputDirectory元素下定义的位置。
与fileSet和file元素不同,dependencySet元素不定义一个具体的路径来选择和复制依赖项。Maven 通过定义的坐标查找工件。如果您只想通过groupId元素和artifactId坐标包含依赖项,那么您可以遵循以下模式:groupdId:artifactId。特定的工件应在 POM 文件的dependencies部分声明,该部分定义了assembly插件。您可以在distribution模块下的 POM 文件中找到以下对rampart模块的依赖定义。如果在同一 POM 文件中定义了同一依赖项的两个版本(这相当不可能),则最后定义的版本将被复制:
<dependency>
<groupId>org.apache.rampart</groupId>
<artifactId>rampart</artifactId>
<type>mar</type>
<version>1.6.1-wso2v12</version>
</dependency>
您还可以通过groupId、artifactId和type包含依赖项,如下面的配置所示。然后,您可以遵循以下模式:groupdId:artifactId:type[:classifier]。这正是先前的示例中遵循的模式:
<includes>
<include>org.apache.rampart:rampart:mar</include>
</includes>
如果您想更精确,也可以将版本包含在模式中。在这种情况下,它将看起来像这样:
<includes>
<include>
org.apache.rampart:rampart:mar:1.6.1-wso2v12
</include>
</includes>
注意
大多数时候我们谈论四个 Maven 坐标;然而,为了精确,实际上有五个。Maven 构件可以通过这五个坐标唯一标识:groupdId:artifactId:type[:classifier]:version。我们已经讨论了四个主要坐标,但没有讨论classifier。这很少使用;在从同一个 POM 文件构建构件但具有多个目标环境的情况下,它可能非常有用。我们将在第七章最佳实践中详细讨论classifiers。
之前的示例仅涵盖了assembly描述符的一个非常小的子集。您可以在maven.apache.org/plugins/maven-assembly-plugin/assembly.html找到所有可用的配置选项,这是一个相当详尽的列表。
提示
在一个名为assembly的目录中包含所有assembly描述符文件是一种最佳实践或约定,尽管这不是强制的。
让我们看看另一个带有Apache Axis2的真实世界示例。Axis2 是一个在 Apache 2.0 许可下发布的开源项目。Axis2 有三种类型的分发:作为 ZIP 文件的二进制分发,作为 WAR 文件的文件分发,以及作为 ZIP 文件的源代码分发。Axis2 的二进制 ZIP 分发可以独立运行,而 WAR 分发必须部署在 Java EE 应用服务器上。
所有三个 Axis2 分发都是从distribution模块内的 POM 文件中创建的,该模块可以在svn.apache.org/repos/asf/axis/axis2/java/core/trunk/modules/distribution/pom.xml找到。
此 POM 文件将 Maven assembly插件的single目标与项目关联,从而启动创建最终分发构件的过程。assembly配置指向三个不同的assembly描述符——一个用于 ZIP 分发,第二个用于 WAR 分发,第三个用于源代码分发。以下代码片段显示了assembly插件的配置:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>distribution-package</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<finalName>axis2-${project.version}</finalName>
<descriptors>
<descriptor>
src/main/assembly/war-assembly.xml
</descriptor>
<descriptor>
src/main/assembly/src-assembly.xml
</descriptor>
<descriptor>
src/main/assembly/bin-assembly.xml
</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
让我们看看bin-assembly.xml文件——构建 ZIP 分发的assembly描述符:
<assembly>
<id>bin</id>
<includeBaseDirectory>true</includeBaseDirectory>
<baseDirectory>axis2-${version}</baseDirectory>
<formats>
<!--<format>tar.gz</format>
//uncomment,if tar.gz archive needed-->
<format>zip</format>
</formats>
这正是我们之前讨论的,也是我们想要避免的,原因与前面代码注释中的相同。如果我们想构建tar.gz分发,那么我们需要修改文件。而不是这样做,我们可以将format配置元素从assembly描述符移动到在pom.xml文件中定义的插件配置中。然后,您可以定义多个配置文件,并根据配置文件配置存档类型:
<fileSets>
………………..
</fileSets>
<dependencySets>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
useProjectArtifact配置元素指示插件是否将在此项目构建中产生的构件包含到dependencySet元素中。通过将值设置为false,我们可以避免它:
<outputDirectory>lib</outputDirectory>
<includes>
<include>*:*:jar</include>
</includes>
<excludes>
<exclude>
org.apache.geronimo.specs:geronimo-activation_1.1_spec:jar
</exclude>
</excludes>
</dependencySet>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<outputDirectory>lib/endorsed</outputDirectory>
<includes>
<include>javax.xml.bind:jaxb-api:jar</include>
</includes>
</dependencySet>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<includes>
<include>org.apache.axis2:axis2-webapp</include>
</includes>
<unpack>true</unpack>
includes和excludes配置元素将确保distribution/pom.xml文件的dependencies部分下定义的所有艺术品都将包含在构建中,除了在excludes配置元素下定义的艺术品。如果你没有定义任何include元素,POM 文件中定义的所有依赖项都将包含在构建中,除了在excludes部分定义的内容。
一旦将unpack配置元素设置为true,在include元素下定义的所有依赖项都将解压缩到outputDirectory。该插件能够解压缩jar、zip、tar.gz和tar.bz存档。以下配置中显示的unpackOptions配置元素可以用来过滤解压缩依赖项的内容。根据以下配置,只有unpackOptions元素下的include元素中定义的文件将被包含;其余的将被忽略,不会包含在构建中。在这个特定情况下,axis2-webapp是一个 WAR 文件(在先前配置的include元素下定义),而distributions/pom.xml文件有对其的依赖。这个 Web 应用将被展开(提取),然后WEB-INF/classes和axis2-web目录中的所有文件将被复制到 ZIP 分发的webapp目录中,包括WEB-INF/web.xml文件:
<outputDirectory>webapp</outputDirectory>
<unpackOptions>
<includes>
<include>WEB-INF/classes/**/*</include>
<include>WEB-INF/web.xml</include>
<include>axis2-web/**/*</include>
</includes>
</unpackOptions>
</dependencySet>
</dependencySets>
</assembly>
现在,让我们看一下war-assembly.xml——构建 WAR 分发的构建描述符。在这个配置中没有新内容,除了outputFileNameMapping配置元素。由于format元素的值设置为zip,这个assembly描述符将生成符合 ZIP 文件规范的存档文件。outputFileNameMapping配置元素的值应用于所有依赖项。默认值是参数化的:${artifactId}-${version}${classifier?}.${extension}。在这种情况下,它是硬编码为axis2.war,所以axis2-webapp艺术品将被复制到outputDirectory元素下定义的位置作为axis2.war。由于没有为outputDirectory元素定义值,文件将被复制到根位置,如下所示:
<assembly>
<id>war</id>
<includeBaseDirectory>false</includeBaseDirectory>
<formats>
<format>zip</format>
</formats>
<dependencySets>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<includes>
<include>org.apache.axis2:axis2-webapp</include>
</includes>
<outputFileNameMapping>axis2.war</outputFileNameMapping>
</dependencySet>
</dependencySets>
<fileSets>
<fileSet>
<directory>../..</directory>
<outputDirectory></outputDirectory>
<includes>
<include>LICENSE.txt</include>
<include>NOTICE.txt</include>
<include>README.txt</include>
<include>release-notes.html</include>
</includes>
<filtered>true</filtered>
</fileSet>
</fileSets>
</assembly>
艺术品/资源过滤
我们在第一个示例中为 WSO2 身份服务器中的assembly插件定义了一个filters配置。这指示assembly插件应用提供的过滤器或过滤器集中定义的文件过滤标准。如果你想要对一个特定的文件应用过滤器,那么你应该将filtered元素的值设置为true。
以下配置展示了如何定义一个过滤器:
<filters>
<filter>${basedir}/src/assembly/filter.properties</filter>
</filters>
让我们来看看文件${basedir}/src/assembly/filter.properties。此文件定义了一组名称/值对。名称是一个特殊的占位符,应在要过滤的文件中用${和}括起来,并在过滤过程中被值替换:
product.name=WSO2 Identity Server
product.key=IS
product.version=5.0.0
hotdeployment=true
hotupdate=true
default.server.role=IdentityServer
组装帮助
正如我们之前讨论的,assembly插件目前只有两个活跃的目标:single和help;所有其他的目标都已弃用。正如我们在前面的示例中看到的,single目标负责创建带有各种其他配置的存档。
以下命令显示了如何执行assembly插件的help目标。这必须在包含 POM 文件的目录中执行:
$ mvn assembly:help -Ddetail=true
当你运行此命令时,如果你看到以下错误,可能不是最新版本。在这种情况下,将插件版本更新到 2.4.1 或更高版本:
[ERROR] Could not find goal 'help' in plugin org.apache.maven.plugins:maven-assembly-plugin:2.2-beta-2 among available goals assembly, attach-assembly-descriptor, attach-component-descriptor, attached, directory-inline, directory, directory-single, single, unpack -> [Help 1]
一个可运行的独立 Maven 项目
由于我们已经涵盖了 Maven assembly插件的许多相关信息,让我们看看如何使用assembly插件构建一个完整的端到端可运行的独立项目。你可以找到完整的示例在svn.wso2.org/repos/wso2/people/prabath/maven-mini/chapter06。按照以下步骤创建一个可运行的独立 Maven 项目:
-
首先,创建一个目录结构,如下所示:
|-pom.xml |-modules |- json-parser |- src/main/java/com/packt/json/JSONParser.java |- pom.xml |- distribution |- src/main/assembly/dist.xml |- pom.xml -
JSONParser.java文件是一个简单的 Java 类,它读取一个 JSON 文件并将其打印到控制台,如下所示:package com.packt.json; import java.io.File; import java.io.FileReader; import org.json.simple.JSONObject; public class JSONParser { public static void main(String[] args) { FileReader fileReader; JSONObject json; org.json.simple.parser.JSONParser parser; parser = new org.json.simple.parser.JSONParser(); try { if (args == null || args.length == 0 || args[0] == null || !new File(args[0]).exists()) { System.out.println("No valid JSON file provided"); } else { fileReader = new FileReader(new File(args[0])); json = (JSONObject) parser.parse(fileReader); if (json != null) { System.out.println(json.toJSONString()); } } } catch (Exception e) { e.printStackTrace(); } } } -
现在,我们可以在
modules/json-parser下创建一个 POM 文件来构建我们的 JAR 文件,如下所示:<project> <modelVersion>4.0.0</modelVersion> <groupId>com.packt</groupId> <artifactId>json-parser</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <name>PACKT JSON Parser</name> <dependencies> <dependency> <groupId>com.googlecode.json-simple </groupId> <artifactId>json-simple</artifactId> <version>1.1</version> </dependency> </dependencies> </project> -
一旦我们完成了
json-parser模块,下一步就是创建distribution模块。distribution模块将有一个 POM 文件和一个assembly描述符。让我们首先在modules/distribution下创建 POM 文件,如下所示。这将与项目关联两个插件:maven-assembly-plugin和maven-jar-plugin。这两个插件都在 Mavendefault生命周期的package阶段执行。由于maven-assembly-plugin在maven-jar-plugin之前定义,因此它将首先执行:<project> <modelVersion>4.0.0</modelVersion> <groupId>com.packt</groupId> <artifactId>json-parser-dist</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <name>PACKT JSON Parser Distribution</name> <dependencies> <!— Under the dependencies section we have to specify all the dependent jars that must be assembled into the final artifact. In this case we have two jar files. The first one is the external dependency that we used to parse the JSON file and the second one includes the class we wrote. --> <dependency> <groupId>com.googlecode.json-simple</groupId> <artifactId>json-simple</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>com.packt</groupId> <artifactId>json-parser</artifactId> <version>1.0.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <id>distribution-package</id> <phase>package</phase> <goals> <goal>single</goal> </goals> <configuration> <finalName>json-parser</finalName> <descriptors> <descriptor> src/main/assembly/dist.xml </descriptor> </descriptors> </configuration> </execution> </executions> </plugin> <!— Even though the maven-jar-plugin is inherited from the super pom, here we have redefined it because we need to add some extra configurations. Since we need to make our final archive executable, we need to define the class to be executable in the jar manifest. Here we have set com.packt.json.JSONParser as our main class. Also – the classpath is set to the lib directory. If you look at the assembly descriptor used in the assembly plugin, you will notice that, the dependent jar files are copied into the lib directory. The manifest configuration in the maven-jar-plugin will result in the following manifest file (META-INF/MANIFEST.MF). Manifest-Version: 1.0 Archiver-Version: Plexus Archiver Created-By: Apache Maven Built-By: prabath Build-Jdk: 1.6.0_65 Main-Class: com.packt.json.JSONParser Class-Path: lib/json-simple-1.1.jar lib/json-parser- 1.0.0.jar --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.3.1</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass>com.packt.json.JSONParser </mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build> </project> -
以下配置显示了
assembly描述符(module/distribution/src/main/assembly/dist.xml),对应于上一步中定义的assembly插件:<assembly> <id>bin</id> <formats> <format>zip</format> </formats> <dependencySets> <dependencySet> <useProjectArtifact>false</useProjectArtifact> <outputDirectory>lib</outputDirectory> <unpack>false</unpack> </dependencySet> </dependencySets> <fileSets> <fileSet> <directory>${project.build.directory}</directory> <outputDirectory></outputDirectory> <includes> <include>*.jar</include> </includes> </fileSet> </fileSets> </assembly> -
现在,我们也完成了
distribution模块。接下来,我们将创建根 POM 文件,它聚合了json-parser和distribution模块,如下所示:<project> <modelVersion>4.0.0</modelVersion> <groupId>com.packt</groupId> <artifactId>json-parser-aggregator</artifactId> <version>1.0.0</version> <packaging>pom</packaging> <name>PACKT JSON Parser Aggregator</name> <modules> <module>modules/json-parser</module> <module>modules/distribution</module> </modules> </project> -
我们已经准备好构建项目了。从根目录,输入
mvn clean install。这将产生位于modules/distribution/target目录中的json-parser-bin.zip存档。输出如下:[INFO] ---------------------------------------------------- [INFO] Reactor Summary: [INFO] [INFO] PACKT JSON Parser............... SUCCESS [ 1.790 s] [INFO] PACKT JSON Parser Distribution.. SUCCESS [ 0.986 s] [INFO] PACKT JSON Parser Aggregator.... SUCCESS [ 0.014 s] [INFO] ---------------------------------------------------- [INFO] BUILD SUCCESS [INFO] ---------------------------------------------------- -
前往
modules/distribution/target并解压json-parser-bin.zip。 -
要运行解析器,请输入以下命令,它将输出
No valid JSON file provided:$ java -jar json-parser/json-parser-dist-1.0.0.jar -
再次运行解析器,并使用有效的 JSON 文件。您需要将 JSON 文件的路径作为参数传递:
$ java -jar json-parser/json-parser-dist-1.0.0.jar myjsonfile.json下面的输出是由前面的命令产生的:
{ "bookName" : "Mastering Maven", "publisher" : "PACKT" }
摘要
在本章中,我们专注于 Maven 的 assembly 插件。assembly 插件提供了一种构建自定义归档文件的方法,聚合了许多其他自定义配置和资源。大多数基于 Java 的产品都使用 assembly 插件来构建最终的分发工件。这些可以是二进制分发、源代码分发,甚至是文档分发。本章详细介绍了如何使用 Maven 的 assembly 插件的实际案例,并最终以一个端到端的示例 Maven 项目结束。
在下一章中,我们将讨论 Maven 的最佳实践。
第七章。最佳实践
在本书中,到目前为止,我们讨论了与 Maven 相关的多数关键概念。在本章中,我们将重点关注与所有这些核心概念相关的最佳实践。以下最佳实践是创建一个成功且高效的构建环境的基本要素。以下标准将帮助您评估您的 Maven 项目的效率,尤其是如果您正在处理一个大规模的多模块项目:
-
开发者开始新项目并添加到构建系统所需的时间
-
升级项目所有模块中依赖项版本所需的努力
-
使用全新的本地 Maven 仓库构建完整项目所需的时间
-
完全离线构建所需的时间
-
更新项目生成的 Maven 工件版本所需的时间;例如,从 1.0.0-SNAPSHOT 到 1.0.0
-
完全新的开发者理解您的 Maven 构建做什么所需的时间
-
引入新的 Maven 仓库所需的努力
-
执行单元测试和集成测试所需的时间
本章的其余部分将讨论 25 个行业公认的最佳实践,这些实践将帮助您提高开发者的生产力并减少任何维护噩梦。
依赖项管理
在以下示例中,您会注意到依赖项版本被添加到应用 POM 文件中定义的每个依赖项:
<dependencies>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>2.26</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
假设您在一个多模块项目中有一组具有相同依赖项集的应用 POM 文件。如果您在每个依赖项中都重复了工件版本,那么要升级到最新依赖项,您需要更新所有 POM 文件,这很容易导致混乱。
不仅如此,如果您在同一个项目的不同模块中使用不同版本的相同依赖项,那么在出现问题时,调试将变成一场噩梦。
使用dependencyManagement,我们可以克服这两个问题。如果是一个多模块 Maven 项目,您需要在父 POM 中引入dependencyManagement,这样它就会被所有其他子模块继承:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>2.26</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
</dependencyManagement>
一旦在dependencyManagement部分下定义了dependencies,如前述代码所示,您只需引用groupId和artifactId元素中的dependency。version元素从相应的dependencyManagement部分中选取:
<dependencies>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
</dependencies>
使用这种方式,如果您想升级或降级一个依赖项,您只需更改dependencyManagement部分下的依赖项版本。
同样的原则也适用于插件。如果您有一组在多个模块中使用的插件,您应该在父模块的pluginManagement部分中定义它们。这样,您只需更改父 POM 中的pluginManagement部分,就可以无缝地降级或升级插件版本,如下述代码所示:
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.4.2</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>2.0-beta-6</version>
</plugin>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>2.0.4</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.13</version>
</plugin>
</plugins>
</pluginManagement>
一旦你在插件管理部分定义了插件,如前述代码所示,你只需引用其groupId(可选)和artifactId元素即可。版本从适当的pluginManagement部分选择:
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>……</executions>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<executions>……</executions>
</plugin>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<executions>……</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<executions>……</executions>
</plugin>
</plugins>
在第四章Maven 插件中详细讨论了 Maven 插件。
定义父模块
在大多数多模块 Maven 项目中,有许多东西是在多个模块之间共享的。依赖版本、插件版本、属性和仓库只是其中的一部分。创建一个名为parent的单独模块并在其 POM 文件中定义所有公共内容是一种常见的(也是最佳)实践。此 POM 文件的打包类型为pom。由pom打包类型生成的工件本身就是一个 POM 文件。
下面是一些 Maven 父模块的示例:
-
Apache Axis2 项目:
svn.apache.org/repos/asf/axis/axis2/java/core/trunk/modules/parent/ -
WSO2 Carbon 项目:
svn.wso2.org/repos/wso2/carbon/platform/trunk/parent/
并非所有项目都遵循这种方法。有些项目只是将父 POM 文件放在根目录下(而不是在parent模块下)。以下是一些示例:
-
Apache Synapse 项目:
svn.apache.org/repos/asf/synapse/trunk/java/pom.xml -
Apache HBase 项目:
svn.apache.org/repos/asf/hbase/trunk/pom.xml
这两种方法都能达到相同的结果,但第一种方法更受欢迎。在第一种方法中,父 POM 文件仅定义了项目内不同 Maven 模块之间的共享资源,同时在项目根目录下还有一个 POM 文件,它定义了要包含在项目构建中的所有模块。在第二种方法中,你将在项目根目录下的同一个 POM 文件中定义所有共享资源以及要包含在项目构建中的所有模块。基于关注点分离原则,第一种方法比第二种方法更好。
POM 属性
你可以在 Maven 应用 POM 文件中使用六种类型的属性:
-
内置属性
-
项目属性
-
本地设置
-
环境变量
-
Java 系统属性
-
自定义属性
始终建议你在应用 POM 文件中使用属性而不是硬编码值。让我们看看几个例子。
让我们考虑 Apache Axis2 分发模块中的应用 POM 文件示例,该模块可在svn.apache.org/repos/asf/axis/axis2/java/core/trunk/modules/distribution/pom.xml找到。这定义了需要在最终分发中包含的 Axis2 项目中创建的所有工件。所有工件都共享相同的groupId元素以及distribution模块的version元素。这在大多数多模块 Maven 项目中是一个常见的场景。大多数模块(如果不是所有模块)都共享相同的groupId和version元素:
<dependencies>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-java2wsdl</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-kernel</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-adb</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
在前面的配置中,Axis2 不是重复version元素,而是使用项目属性${project.version}。当 Maven 发现这个项目属性时,它会从项目 POM 的version元素中读取值。如果项目 POM 文件没有version元素,那么 Maven 将尝试从直接父 POM 文件中读取它。这里的优点是,当有一天你升级项目版本时,你只需要升级distribution POM 文件的version元素(或其父元素)。
前面的配置并不完美;它可以进一步改进如下:
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>axis2-java2wsdl</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>axis2-kernel</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>axis2-adb</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
在这里,我们还用项目属性${project.groupid}替换了所有依赖项中硬编码的groupId元素值。当 Maven 发现这个项目属性时,它会从项目 POM 的groupId元素中读取值。如果项目 POM 文件没有groupId元素,那么 Maven 将尝试从直接父 POM 文件中读取它。
这里列出了 Maven 的一些内置属性和项目属性:
-
project.version:这指的是项目 POM 文件中version元素的值 -
project.groupId:这指的是项目 POM 文件中groupId元素的值 -
project.artifactId:这指的是项目 POM 文件中artifactId元素的值 -
project.name:这指的是项目 POM 文件中name元素的值 -
project.description:这指的是项目 POM 文件中description元素的值 -
project.basedir:这指的是项目基本目录的路径以下是一个示例,展示了这个项目属性的用法。在这里,我们有一个系统依赖,需要从文件
system路径中引用:<dependency> <groupId>org.apache.axis2.wso2</groupId> <artifactId>axis2</artifactId> <version>1.6.0.wso2v2</version> <scope>system</scope> <systemPath>${project.basedir}/ lib/axis2-1.6.jar</systemPath> </dependency>
除了项目属性之外,你还可以从USER_HOME/.m2/settings.xml文件中读取属性。例如,如果你想读取本地 Maven 仓库的路径,你可以使用属性${settings.localRepository}。同样,使用相同的模式,你可以读取settings.xml文件中定义的任何配置元素。
在系统定义的环境变量可以使用应用程序 POM 文件中的 env 前缀读取。${env.M2_HOME} 属性将返回 Maven 主目录的路径,而 ${env.java_home} 返回 Java 主目录的路径。这些属性在特定的 Maven 插件中非常有用。
Maven 还允许你定义自己的自定义属性集。自定义属性主要用于定义依赖项版本。
你不应该将自定义属性散布在各个地方。在多模块 Maven 项目中定义它们的理想位置是父 POM 文件,然后所有其他子模块将继承这些属性。
如果你查看 WSO2 Carbon 项目的父 POM 文件,你会找到一个大量自定义属性的定义(svn.wso2.org/repos/wso2/carbon/platform/branches/turing/parent/pom.xml)。以下代码块包含了一些这些自定义属性:
<properties>
<rampart.version>1.6.1-wso2v10</rampart.version>
<rampart.mar.version>1.6.1-wso2v10</rampart.mar.version>
<rampart.osgi.version>1.6.1.wso2v10</rampart.osgi.version>
</properties>
当你向 Rampart jar 添加依赖项时,你不需要在那里指定版本。只需通过 ${rampart.version} 属性名称引用它。此外,请记住,所有自定义定义的属性都是继承的,并且可以在任何子 POM 文件中覆盖:
<dependency>
<groupId>org.apache.rampart.wso2</groupId>
<artifactId>rampart-core</artifactId>
<version>${rampart.version}</version>
</dependency>
避免重复的 groupIds 和版本,并从父 POM 继承
在一个多模块 Maven 项目中,大多数模块(如果不是所有模块)共享相同的 groupId 和 version 元素。在这种情况下,你可以避免在你的应用程序 POM 文件中添加 version 和 groupId 元素,因为这些将自动从相应的父 POM 继承。
如果你查看 axis2-kernel(它是 Apache Axis2 项目的模块),你会发现没有定义 groupId 或 version 元素:(svn.apache.org/repos/asf/axis/axis2/java/core/trunk/modules/kernel/pom.xml)。Maven 从父 POM 文件中读取它们:
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-parent</artifactId>
<version>1.7.0-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>axis2-kernel</artifactId>
<name>Apache Axis2 - Kernel</name>
</project>
遵循命名约定
当定义你的 Maven 项目的坐标时,你必须始终遵循命名约定。
groupId 元素的值应该遵循你在 Java 包名称中使用的相同命名约定。它必须是一个域名(域名的反转)——你拥有它——或者至少你的项目是在其下开发的。
以下列表涵盖了部分 groupId 命名约定:
-
groupId元素的名称必须是小写。 -
使用可以唯一标识你的项目的域名反转。这也有助于避免不同项目产生的工件之间的冲突。
-
避免使用数字或特殊字符(例如,
org.wso2.carbon.identity-core)。 -
不要尝试通过驼峰式命名将两个单词组合成一个单词(例如,
org.wso2.carbon.identityCore)。 -
确保同一公司内不同团队开发的子项目最终都继承自相同的
groupId,并扩展父groupId的名称,而不是定义自己的。
让我们通过一些例子来了解一下。你会注意到,所有在Apache 软件基金会(ASF)下开发的开源项目都使用相同的父groupId(org.apache)并定义自己的groupId,该groupId从父groupId扩展而来:
-
Apache Axis2 项目:
org.apache.axis2,继承自父groupIdorg.apache -
Apache Synapse 项目:
org.apache.synapse,继承自父groupIdorg.apache -
Apache ServiceMix 项目:
org.apache.servicemix,继承自父groupIdorg.apache -
WSO2 Carbon 项目:
org.wso2.carbon
除了groupId之外,在定义artifactIds时也应遵循命名规范。
下面的列表列出了一些artifactId的命名规范:
-
artifactId的名称必须全部小写。 -
避免在
artifactId内部重复groupId的值。如果你发现需要在artifactId元素的开头使用groupId元素并在末尾添加一些内容,那么你需要重新审视你的项目结构。你可能需要添加更多的模块组。 -
避免使用特殊字符(例如,#、$、&、%)。
-
不要尝试通过驼峰命名法将两个单词组合成一个单词(例如,identityCore)。
以下version的命名规范同样重要。给定 Maven 艺术品的版本可以分成四个部分:
<Major version>.<Minor version>.<Incremental version>-<Build number or the qualifier>
主版本号反映了新主要功能的引入。给定艺术品的版本号的变化也可能意味着新更改不一定与之前发布的艺术品向后兼容。次版本号以向后兼容的方式反映了在之前发布的版本中引入的新功能。增量版本反映了修复错误的版本。构建号可以是源代码仓库的修订号。
这种版本规范不仅适用于 Maven 艺术品。苹果在 2014 年 9 月发布了一个 iOS 移动操作系统的重大版本:iOS 8.0.0。发布后不久,他们发现了一个影响蜂窝网络连接和 iPhone 的 TouchID 的严重错误。然后他们发布了 iOS 8.0.1 作为补丁版本来修复这些问题。
让我们通过一些例子来了解一下:
-
Apache Axis2 1.6.0 版本发布:
svn.apache.org/repos/asf/axis/axis2/java/core/tags/v1.6.0/pom.xml。 -
Apache Axis2 1.6.2 版本发布:
svn.apache.org/repos/asf/axis/axis2/java/core/tags/v1.6.2/pom.xml。 -
Apache Axis2 1.7.0-SNAPSHOT:
svn.apache.org/repos/asf/axis/axis2/java/core/trunk/pom.xml。 -
Apache Synapse 2.1.0-wso2v5:
svn.wso2.org/repos/wso2/tags/carbon/3.2.3/dependencies/synapse/2.1.0-wso2v5/pom.xml。在这里,synapse 代码是在 WSO2 源代码库中维护的,而不是在 Apache 下。在这种情况下,我们使用 wso2v5 分类器使其与 Apache Synapse 产生的相同工件区分开来。
在编写自己的插件之前,请三思而后行。你可能并不需要它!
Maven 的一切都是关于插件的!几乎你可以找到完成任何所需任务的一个插件。如果你发现需要编写一个插件,花些时间在网上做一些研究,看看是否可以找到类似的东西——可能性非常高。你还可以在maven.apache.org/plugins找到可用的 Maven 插件列表。
Maven 发布插件
发布一个项目需要执行许多重复的任务。Maven release插件的目的是自动化这些任务。发布插件定义了以下八个目标,这些目标分为两个阶段执行——准备发布和执行发布:
-
release:clean:这会在发布准备之后进行清理。 -
release:prepare:这为软件配置管理(SCM)中的发布做准备。 -
release:prepare-with-pom:这为 SCM 中的发布做准备,并通过完全解析依赖项生成发布 POM。 -
release:rollback:这会回滚到之前的发布。 -
release:perform:这从 SCM 执行发布。 -
release:stage:这从 SCM 执行发布到临时文件夹或仓库。 -
release:branch:这创建了一个包含所有版本更新的当前项目分支。 -
release:update-versions:这更新 POM 中的版本。
准备阶段将使用release:prepare目标完成以下任务:
-
确认源代码中的所有更改都已提交。
-
确保没有 SNAPSHOT 依赖项。在项目开发阶段,我们使用 SNAPSHOT 依赖项,但在发布时,所有依赖项都应该更改为已发布的版本。
-
项目 POM 文件的版本将从 SNAPSHOT 更改为具体的版本号。
-
项目 POM 中的 SCM 信息将更改,以包括标记的最终目的地。
-
对修改后的 POM 文件执行所有测试。
-
将修改后的 POM 文件提交到 SCM,并使用版本名称标记代码。
-
将主分支中的 POM 文件版本更改为 SNAPSHOT 版本,然后将修改后的 POM 文件提交到主分支。
最后,将使用release:perform目标执行发布。这将从 SCM 中的发布标签检出代码,并运行一系列预定义的目标:site和deploy-site。
maven-release-plugin 在父 POM 中未定义,应在您的项目 POM 文件中显式定义。releaseProfiles 配置元素定义了要发布的配置文件,而 goals 配置元素定义了在 release:perform 期间要执行的插件目标。在以下配置中,maven-deploy-plugin 的 deploy 目标和 maven-assembly-plugin 的 single 目标将被执行:
<plugin>
<artifactId>maven-release-plugin</artifactId>
<version>2.5</version>
<configuration>
<releaseProfiles>release</releaseProfiles>
<goals>deploy assembly:single</goals>
</configuration>
</plugin>
注意
有关 Maven Release 插件的更多详细信息,请参阅 maven.apache.org/maven-release/maven-release-plugin/。
Maven enforcer 插件
Maven Enforce 插件允许您控制或强制构建环境中的约束。这些可能包括 Maven 版本、Java 版本、操作系统参数,甚至是用户定义的规则。
该插件定义了两个目标:enforce 和 displayInfo。enforcer:enforce 目标将对多模块 Maven 项目的所有模块执行所有定义的规则,而 enforcer:displayInfo 将显示项目与标准规则集的合规性细节。
maven-enforcer-plugin 在父 POM 中未定义,应在您的项目 POM 文件中显式定义:
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.3.1</version>
<executions>
<execution>
<id>enforce-versions</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireMavenVersion>
<version>3.2.1</version>
</requireMavenVersion>
<requireJavaVersion>
<version>1.6</version>
</requireJavaVersion>
<requireOS>
<family>mac</family>
</requireOS>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
上述插件配置强制 Maven 版本为 3.2.1,Java 版本为 1.6,操作系统为 Mac 系列。
Apache Axis2 项目使用 enforcer 插件确保没有应用程序 POM 文件定义 Maven 仓库。Axis2 所需的所有工件都应位于 Maven 中央仓库中。以下配置元素是从 svn.apache.org/repos/asf/axis/axis2/java/core/trunk/modules/parent/pom.xml 中提取的。在这里,它禁止所有仓库和插件仓库,除了快照仓库:
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireNoRepositories>
<banRepositories>true</banRepositories>
<banPluginRepositories>true</banPluginRepositories>
<allowSnapshotRepositories>true
</allowSnapshotRepositories>
<allowSnapshotPluginRepositories>true
</allowSnapshotPluginRepositories>
</requireNoRepositories>
</rules>
</configuration>
</execution>
</executions>
</plugin>
注意
除了 enforcer 插件附带的标准规则集外,您还可以定义自己的规则。有关如何编写自定义规则的更多详细信息,请参阅 maven.apache.org/enforcer/enforcer-api/writing-a-custom-rule.html。
避免使用未指定版本的插件
如果您已将插件与您的应用程序 POM 关联,但没有指定版本,那么 Maven 将下载相应的 maven-metadata.xml 文件并将其存储在本地。只有插件的最新发布版本将被下载并用于项目。这可能会轻易地产生不确定性。您的项目可能使用当前版本的插件运行良好,但后来,如果相同的插件有新的发布版本,您的 Maven 项目将自动开始使用最新的版本。这可能会导致不可预测的行为,并导致调试混乱。
小贴士
始终建议您在插件配置中指定插件版本。
你可以使用 Maven enforcer插件强制执行此规则,如下所示:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.3.1</version>
<executions>
<execution>
<id>enforce-plugin-versions</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requirePluginVersions>
<message>………… <message>
<banLatest>true</banLatest>
<banRelease>true</banRelease>
<banSnapshots>true</banSnapshots>
<phases>clean,deploy,site</phases>
<additionalPlugins>
<additionalPlugin>
org.apache.maven.plugins:maven-eclipse-plugin
</additionalPlugin>
<additionalPlugin>
org.apache.maven.plugins:maven-reactor-plugin
</additionalPlugin>
</additionalPlugins>
<unCheckedPluginList>
org.apache.maven.plugins:maven-enforcer-plugin,
org.apache.maven.plugins:maven-idea-plugin
</unCheckedPluginList>
</requirePluginVersions>
</rules>
</configuration>
</execution>
</executions>
</plugin>
以下是对前面代码中定义的每个关键配置元素的说明:
-
message:此选项用于定义用户可选的消息,如果规则执行失败。 -
banLatest:此选项用于限制任何插件使用"Latest"作为版本。 -
banRelease:此选项用于限制任何插件使用"RELEASE"作为版本。 -
banSnapshots:此选项用于限制使用 SNAPSHOT 插件。 -
banTimestamps:此选项用于限制使用带时间戳版本的 SNAPSHOT 插件。 -
phases:这是一个以逗号分隔的阶段列表,用于查找生命周期插件绑定。默认值是"clean,deploy,site"。 -
additionalPlugins:这是一个额外的插件列表,用于强制执行版本。这些插件可能没有在应用程序 POM 文件中定义,但仍然被使用,如 help、eclipse 等。插件应以groupId:artifactId的形式指定。 -
unCheckedPluginList:这是一个以逗号分隔的插件列表,用于跳过版本检查。
注意
你可以阅读更多关于requirePluginVersions规则的信息,请参阅maven.apache.org/enforcer/enforcer-rules/requirePluginVersions.html。
描述性父 POM 文件
确保你的项目父 POM 文件描述足够详细,以便列出项目所做的工作、开发者及贡献者、他们的联系方式、项目工件发布的许可证、报告问题的位置等信息。以下是一个描述性良好的 POM 文件示例,可在svn.apache.org/repos/asf/axis/axis2/java/core/trunk/modules/parent/pom.xml找到:
<project>
<name>Apache Axis2 - Parent</name>
<inceptionYear>2004</inceptionYear>
<description>Axis2 is an effort to re-design and totally re-
implement both Axis/Java……</description>
<url>http://axis.apache.org/axis2/java/core/</url>
<licenses><license>http://www.apache.org/licenses/LICENSE-2.0.html</license></licenses>
<issueManagement>
<system>jira</system>
<url>http://issues.apache.org/jira/browse/AXIS2</url>
</issueManagement>
<mailingLists>
<mailingList>
<name>Axis2 Developer List</name>
<subscribe>java-dev-subscribe@axis.apache.org</subscribe>
<unsubscribe>java-dev-unsubscribe@
axis.apache.org</unsubscribe>
<post>java-dev@axis.apache.org</post>
<archive>http://mail-archives.apache.org/mod_mbox/axis-java-dev/</archive>
<otherArchives>
<otherArchive>http://markmail.org/search/list:
org.apache.ws.axis-dev</otherArchive>
</otherArchives>
</mailingList>
<developers>
<developer>
<name>Sanjiva Weerawarana</name>
<id>sanjiva</id>
<email>sanjiva AT wso2.com</email>
<organization>WSO2</organization>
</developer>
<developers>
<contributors>
<contributor>
<name>Dobri Kitipov</name>
<email>kdobrik AT gmail.com</email>
<organization>Software AG</organization>
</contributor>
</contributors>
</project>
文档是你的朋友
如果你是一个优秀的开发者,你应该知道文档的价值。你所写的一切都不应该是晦涩难懂的,或者只有你自己能理解。让它成为 Java、.NET、C++项目,或者 Maven 项目——文档是你的朋友。带有良好文档的代码非常易于阅读。如果你在应用程序 POM 文件中添加的任何配置都不是自我描述的,请确保至少添加一行注释来解释其功能。
下面将跟随一些来自 Apache Axis2 项目的良好示例:
<profile>
<id>java16</id>
<activation>
<jdk>1.6</jdk>
</activation>
<!-- JDK 1.6 build still use JAX-WS 2.1 because integrating
Java endorsed mechanism with Maven is bit of complex -
->
<properties>
<jaxb.api.version>2.1</jaxb.api.version>
<jaxbri.version>2.1.7</jaxbri.version>
<jaxws.tools.version>2.1.3</jaxws.tools.version>
<jaxws.rt.version>2.1.3</jaxws.rt.version>
</properties>
</profile>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<!-- Minimum required version here is 2.2-beta-4 because
org.apache:apache:7 uses the runOnlyAtExecutionRoot
parameter, which is not supported in earlier
versions. -->
<version>2.2-beta-5</version>
<configuration>
<!-- Workaround for MASSEMBLY-422 / MASSEMBLY-449 -->
<archiverConfig>
<fileMode>420</fileMode><!-- 420(dec)=644(oct) -->
<directoryMode>493</directoryMode><!--493(dec)=755(oct)-->
<defaultDirectoryMode>493</defaultDirectoryMode>
</archiverConfig>
</configuration>
</plugin>
<!-- No chicken and egg problem here because the plugin doesn't expose any extension. We can always use the version from the current build. -->
<plugin>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-repo-maven-plugin</artifactId>
<version>${project.version}</version>
</plugin>
避免覆盖默认的目录结构
Maven 遵循设计哲学 约定优于配置。在没有任何配置更改的情况下,Maven 假设源代码的位置是 ${basedir}/src/main/java,测试的位置是 ${basedir}/src/test/java,资源位于 ${basedir}/src/main/resources。在构建成功后,Maven 知道编译后的类放在哪里(${basedir}/target/classes)以及最终工件应该复制到哪(${basedir}/target/)。虽然可以更改这种目录结构,但建议不要这样做。为什么?
保持默认结构可以提高项目的可读性。即使是一个新手开发者,如果他熟悉 Maven,也知道该往哪里找。此外,如果你已经将插件和其他 Maven 扩展与你的项目关联,那么如果你没有更改默认的 Maven 目录结构,你将能够以最小的更改使用它们。大多数这些插件和其他扩展默认假设 Maven 约定。
在开发过程中使用 SNAPSHOT 版本控制
如果你的项目工件仍在开发中并且定期部署到 Maven 快照仓库,你应该使用 SNAPSHOT 限定符。如果即将发布的版本是 1.7.0,那么在开发期间你应该使用版本 1.7.0-SNAPSHOT。Maven 对 SNAPSHOT 版本有特殊处理。如果你尝试将 1.7.0-SNAPSHOT 部署到仓库,Maven 会首先将 SNAPSHOT 限定符扩展为 UTC(协调世界时)的日期和时间值。如果部署时的日期/时间是 2014 年 11 月 10 日上午 10:30,那么 SNAPSHOT 限定符将被替换为 20141110-103005-1,并且工件将以版本 1.7.0-20141110-103005-1 部署。
移除未使用的依赖项
总是确保你维护一个干净的应用程序 POM 文件。你不应该定义或使用任何未声明的依赖项。Maven 的 dependency 插件帮助你识别此类差异。
maven-dependency-plugin 在父 POM 中未定义,应在你的项目 POM 文件中显式定义:
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.0</version>
</plugin>
一旦将前面的配置添加到你的应用程序 POM 文件中,你需要运行 dependency 插件的 analyze 目标来针对你的 Maven 项目:
$ mvn dependency:analyze
在这里,你可以看到一份样本输出,它抱怨一个未使用的声明依赖项:
[WARNING] Unused declared dependencies found:
[WARNING] org.apache.axis2:axis2-kernel:jar:1.6.2:compile
注意
关于 Maven 依赖插件的更多详细信息可在 maven.apache.org/plugins/maven-dependency-plugin/ 找到。
避免在应用程序 POM 文件中保留凭证
在 Maven 构建过程中,你需要连接到防火墙外的外部仓库。在一个高度安全的环境中,任何出站连接都必须通过内部代理服务器。以下 MAVEN_HOME/conf/settings.xml 中的配置显示了如何通过安全的代理服务器连接到外部仓库:
<proxy>
<id>internal_proxy</id>
<active>true</active>
<protocol>http</protocol>
<username>proxyuser</username>
<password>proxypass</password>
<host>proxy.host.net</host>
<port>80</port>
<nonProxyHosts>local.net|some.host.com</nonProxyHosts>
</proxy>
此外,Maven 仓库可以受到合法访问的保护。如果某个仓库通过 HTTP 基本身份验证进行保护,那么相应的凭据应该在MAVEN_HOME/conf/settings.xml的server元素下定义如下:
<server>
<id>central</id>
<username>my_username</username>
<password>my_password</password>
</server>
在配置文件中明文存储机密数据是一种安全威胁,必须避免。Maven 提供了一种在settings.xml中加密配置数据的方法。
首先,我们需要创建一个主加密密钥,如下所示:
$ mvn -emp mymasterpassword
{lJ1MrCQRnngHIpSadxoyEKyt2zIGbm3Yl0ClKdTtRR6TleNaEfGOEoJaxNcdMr+G}
使用上述命令的输出,我们需要在USER_HOME/.m2/下创建一个名为settings-security.xml的文件,并将加密的主密码添加到其中,如下所示:
<settingsSecurity>
<master>
{lJ1MrCQRnngHIpSadxoyEKyt2zIGbm3Yl0ClKdTtRR6TleNaEfGOEoJaxNcdMr+G}
</master>
</settingsSecurity>
一旦正确配置了主密码,我们就可以开始加密settings.xml中的其余机密数据。让我们看看如何加密服务器密码。首先,我们需要使用以下命令生成明文密码的加密密码。注意,之前我们使用了 emp(加密主密码),而现在我们使用 ep(加密密码):
$ mvn -ep my_password
{PbYw8YaLb3cHA34/5EdHzoUsmmw/u/nWOwb9e+x6Hbs=}
复制加密密码的值,并将其替换为settings.xml中相应的值:
<server>
<id>central</id>
<username>my_username</username>
<password>
{PbYw8YaLb3cHA34/5EdHzoUsmmw/u/nWOwb9e+x6Hbs=}
</password>
</server>
避免使用已弃用的引用
从 Maven 3.0 开始,所有以pom.*开头的属性都已弃用。避免使用任何已弃用的 Maven 属性,如果你已经使用了它们,确保迁移到等效的属性。
避免重复 - 使用原型
当我们创建一个 Java 项目时,需要根据项目的类型以不同的方式对其进行结构化。如果它是一个 Java EE 网络应用程序,那么我们需要有一个 WEB-INF 目录和一个web.xml文件。如果它是一个 Maven 插件项目,我们需要有一个 Mojo 类,该类从org.apache.maven.plugin.AbstractMojo扩展。由于每种类型的项目都有自己的预定义结构,为什么每个人都必须一次又一次地构建相同结构呢?为什么我们不从一个模板开始呢?每个项目都可以有自己的模板,开发者可以根据他们的需求扩展模板。Maven 原型解决了这个问题。每个原型都是一个项目模板。
我们在第三章中详细讨论了 Maven 原型。
避免使用 maven.test.skip
你可能可以管理一个非常小的项目,这个项目没有太多的发展,而不需要单元测试。但是,任何大规模的项目都不能没有单元测试。单元测试提供了第一层保证,确保你不会因为引入新的代码更改而破坏任何现有功能。在理想情况下,你不应该在没有使用单元测试构建完整项目的情况下将任何代码提交到源代码库。
Maven 使用surefire插件来运行测试,并且作为不良做法,开发者习惯于通过将maven.test.skip属性设置为true来跳过单元测试的执行,如下所示:
$ mvn clean install –Dmaven.test.skip=true
这可能导致项目后期出现严重后果,您必须确保所有开发人员在构建过程中不要跳过测试。
使用 Maven enforcer插件的requireProperty规则,您可以禁止开发人员使用maven.test.skip属性。
以下展示了您需要添加到应用程序 POM 文件中的enforcer插件配置:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.3.1</version>
<executions>
<execution>
<id>enforce-property</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireProperty>
<property>maven.test.skip</property>
<message>maven.test.skip must be specified</message>
<regex>false</regex>
<regexMessage>You cannot skip tests</regexMessage>
</requireProperty>
</rules>
<fail>true</fail>
</configuration>
</execution>
</executions>
</plugin>
现在,如果您对项目运行mvn clean install,您将看到以下错误信息:
maven.test.skip must be specified
这意味着您需要在每次运行mvn clean install时指定Dmaven.test.skip=false:
$ mvn clean install –Dmaven.test.skip=false
但如果您设置了–Dmaven.test.skip=true,那么您将看到以下错误:
You cannot skip tests
尽管如此,每次运行构建时都输入–Dmaven.test.skip=false可能会让您感到有些烦恼。为了避免这种情况,在您的应用程序 POM 文件中添加属性maven.test.skip并将其值设置为false:
<project>
<properties>
<maven.test.skip>false</maven.test.skip>
</properties>
</project>
注意
关于requireProperty规则的更多详细信息,请参阅maven.apache.org/enforcer/enforcer-rules/requireProperty.html。
摘要
在本章中,我们探讨了并强调了在大型开发项目中遵循的一些最佳实践。本书前几章详细讨论了这里强调的大部分内容。始终推荐遵循最佳实践,因为它将极大地提高开发人员的工作效率,并减少任何维护噩梦。
总体而言,本书涵盖了 Apache Maven 3 及其核心概念,包括 Maven 原型、插件、构建和生命周期,并通过示例进行了说明。


浙公网安备 33010602011771号