Maven 入门指南
本指南旨在为首次使用 Maven 的人员提供参考,同时也旨在作为一本包含自包含参考和常见用例解决方案的食谱。对于初学者,建议您按顺序逐步学习材料。对于熟悉 Maven 的用户,本指南力求为当前需求提供快速解决方案。在此假设您已下载并安装了 Maven 到本地机器上。如果您尚未这样做,请参阅下载和安装说明。
好的,现在您已经安装了 Maven,我们准备开始。在我们进入示例之前,我们将非常简要地介绍一下 Maven 是什么以及它如何帮助您在日常工作和与团队成员的协作中。当然,Maven 适用于小型项目,但在帮助团队更有效地运作方面,Maven 尤为出色,它允许团队成员专注于项目利益相关者所需的内容。您可以把构建基础设施留给 Maven!
章节
- 什么是 Maven?
- 如何 Maven 能给我的开发过程带来好处?
- 如何设置 Maven?
- 如何创建我的第一个 Maven 项目?
- 如何编译我的应用程序源代码?
- 如何编译我的测试源代码并运行单元测试?
- 如何创建 JAR 并在本地仓库中安装它?
- 什么是快照版本?
- 如何使用插件?
- 如何向我的 JAR 文件添加资源?
- 如何过滤资源文件?
- 如何使用外部依赖项?
- 如何在我的远程仓库中部署我的 jar 文件?
- 如何创建文档?
- 如何构建其他类型的工程?
- 如何同时构建多个项目?
什么是 Maven?
初看之下,Maven 似乎可以代表许多事物,但简而言之,Maven 是一种尝试将模式应用于项目的构建基础设施,通过提供最佳实践的清晰路径来促进理解和生产力。本质上,Maven 是一个项目管理和理解工具,因此提供了一种帮助管理的方法:
- 构建
- 文档
- 报告
- 依赖关系
- 供应链管理
- 发布
- 分布
如果您想了解更多关于 Maven 的背景信息,可以查阅《Maven 的哲学》和《Maven 的历史》。现在让我们继续探讨用户如何从使用 Maven 中受益。
如何 Maven 能给我的开发过程带来好处?
Maven 可以通过采用标准约定和实践来加速您的开发周期,同时帮助您实现更高的成功率。
现在我们已经介绍了一些关于 Maven 的历史和目的,接下来让我们通过一些实际例子来让您上手使用 Maven!
如何设置 Maven?
Maven 的默认设置通常足够,但如果你需要更改缓存位置或处于 HTTP 代理之后,你需要创建配置。请参阅配置 Maven 的指南以获取更多信息。
如何创建我的第一个 Maven 项目?
我们将一头扎进创建您的第一个 Maven 项目!为了创建我们的第一个 Maven 项目,我们将使用 Maven 的模板机制。模板被定义为所有同类事物制作的原型或模型。在 Maven 中,模板是一个项目的模板,结合一些用户输入,生成一个符合用户需求的、可工作的 Maven 项目。现在我们将向您展示模板机制是如何工作的,但如果您想了解更多关于模板的信息,请参阅我们的《模板简介》。
继续创建您的第一个项目!为了创建最简单的 Maven 项目,请在命令行中执行以下操作:
mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.5 -DinteractiveMode=false
一旦执行此命令,您将注意到发生了一些事情。首先,您会注意到为新的项目创建了一个名为 my-app
的目录,并且这个目录包含一个名为 pom.xml
的文件,其外观应如下所示:
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.mycompany.app</groupId>
- <artifactId>my-app</artifactId>
- <version>1.0-SNAPSHOT</version>
- <name>my-app</name>
- <!-- FIXME change it to the project's website -->
- <url>http://www.example.com</url>
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <maven.compiler.release>17</maven.compiler.release>
- </properties>
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.junit</groupId>
- <artifactId>junit-bom</artifactId>
- <version>5.11.0</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.junit.jupiter</groupId>
- <artifactId>junit-jupiter-api</artifactId>
- <scope>test</scope>
- </dependency>
- <!-- Optionally: parameterized tests support -->
- <dependency>
- <groupId>org.junit.jupiter</groupId>
- <artifactId>junit-jupiter-params</artifactId>
- <scope>test</scope>
- </dependency>
- </dependencies>
- <build>
- <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
- ... lots of helpful plugins
- </pluginManagement>
- </build>
- </project>
pom.xml
包含本项目的项目对象模型(POM)。POM 是 Maven 中的基本工作单元。这一点很重要,因为 Maven 本质上是以项目为中心的,一切围绕项目这一概念展开。简而言之,POM 包含了关于您项目的所有重要信息,并且是查找与项目相关任何内容的一站式服务。理解 POM 非常重要,建议新用户参考《POM 简介》。
这是一个非常简单的 POM,但仍展示了每个 POM 包含的关键元素,因此让我们逐一了解它们,以便您熟悉 POM 的基本要素:
- 项目 这是所有 Maven
pom.xml
文件中的顶级元素。 - 模型版本 此元素指示此 POM 使用对象模型的哪个版本。模型本身的版本变化非常不频繁,但如果 Maven 开发者认为有必要更改模型,则确保使用稳定性的版本是强制性的。
- groupId 此元素表示创建项目的组织或组的唯一标识符。groupId 是项目的关键标识符之一,通常基于您组织的完全限定域名。例如,
org.apache.maven.plugins
是所有 Maven 插件指定的 groupId。 - artifactId 此元素表示由本项目生成的首要实体的唯一基本名称。项目的首要实体通常是 JAR 文件。像源包这样的次要实体也使用 artifactId 作为其最终名称的一部分。Maven 生成的典型实体形式为 -.(例如,
myapp-1.0.jar
)。 - 版本 此元素表示项目生成的工件版本。Maven 在版本管理方面提供了很大的帮助,你经常会看到一个
SNAPSHOT
标识符在版本中,这表示项目处于开发状态。我们将在本指南的后续部分进一步讨论快照的使用及其工作原理。 - 名称 此元素表示用于项目的显示名称。这通常用于 Maven 生成的文档中。
- url 此元素指示项目的站点位置。这通常用于 Maven 生成的文档中。
- 属性 此元素包含在 POM 内部任何地方都可访问的值占位符。
- 依赖项 此元素的孩子列表包含依赖项。POM 的基石。
- 构建此元素处理诸如声明您的项目目录结构和管理插件等问题。
关于 POM 中可用元素的全部参考,请参阅我们的 POM 参考。现在让我们回到手头的项目。
在您第一个项目的原型生成之后,您也会注意到以下目录结构已被创建:
my-app |-- pom.xml `-- src |-- main | `-- java | `-- com | `-- mycompany | `-- app | `-- App.java `-- test `-- java `-- com `-- mycompany `-- app `-- AppTest.java
如您所见,从原型创建的项目包含一个 POM、一个用于应用程序源代码的源树以及一个用于测试源代码的源树。这是 Maven 项目的标准布局(应用程序源代码位于 ${project.basedir}/src/main/java
,测试源代码位于 ${project.basedir}/src/test/java
,其中 ${project.basedir}
代表包含 pom.xml
的目录)。
如果您要手动创建一个 Maven 项目,我们推荐使用的目录结构如下。这是一个 Maven 约定,要了解更多信息,您可以阅读我们的《标准目录布局简介》。
既然我们已经有了 POM、一些应用程序源和一些测试源,你可能想知道...
如何编译我的应用程序源代码?
切换到由 archetype:generate 创建 pom.xml
的目录,执行以下命令以编译您的应用程序源代码:
mvn compile
在执行此命令后,您应该看到以下类似的输出:
[INFO] Scanning for projects... [INFO] [INFO] ----------------------< com.mycompany.app:my-app >---------------------- [INFO] Building my-app 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ my-app --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] skip non existing resourceDirectory <dir>/my-app/src/main/resources [INFO] [INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ my-app --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 1 source file to <dir>/my-app/target/classes [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 0.899 s [INFO] Finished at: 2020-07-12T11:31:54+01:00 [INFO] ------------------------------------------------------------------------
第一次执行此命令(或任何其他命令),Maven 需要下载执行该命令所需的所有插件和相关依赖。从 Maven 的全新安装开始,这可能需要相当长的时间(在上面的输出中,耗时近 4 分钟)。如果您再次执行该命令,Maven 现在将拥有所需的一切,因此不需要下载任何新内容,并且能够更快地执行该命令。
从输出中可以看出,编译后的类被放置在 ${project.basedir}/target/classes
,这是 Maven 使用的另一个标准约定。所以,如果你是一个敏锐的观察者,你会注意到,通过使用标准约定,上面的 POM 非常小,你不必明确告诉 Maven 任何源文件的位置或输出应该去哪里。遵循 Maven 的标准约定,你可以用很少的努力完成很多事情!仅作为一个随意的比较,让我们看看在 Ant 中你可能需要做什么才能完成同样的事情。
现在,这只是为了编译一个单一的应用源树,上面显示的 Ant 脚本几乎与上面显示的 POM 大小相同。但我们将看看我们还能用这么简单的 POM 做多少!
如何编译我的测试源代码并运行单元测试?
现在你已经成功编译了应用程序的源代码,现在你有一些单元测试想要编译和执行(因为每个程序员都会编写和执行他们的单元测试*暗示 暗示*)。
执行以下命令:
mvn test
在执行此命令后,您应该看到以下类似的输出:
[INFO] Scanning for projects... [INFO] [INFO] ----------------------< com.mycompany.app:my-app >---------------------- [INFO] Building my-app 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ my-app --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] skip non existing resourceDirectory <dir>/my-app/src/main/resources [INFO] [INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ my-app --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ my-app --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] skip non existing resourceDirectory <dir>/my-app/src/test/resources [INFO] [INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ my-app --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 1 source file to <dir>/my-app/target/test-classes [INFO] [INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ my-app --- [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.mycompany.app.AppTest [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.025 s - in com.mycompany.app.AppTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1.881 s [INFO] Finished at: 2020-07-12T12:00:33+01:00 [INFO] ------------------------------------------------------------------------
关于输出的注意事项:
- Maven 这次下载了更多的依赖。这些是执行测试所需的依赖和插件(它已经拥有了编译所需的依赖,不会再次下载)。
- the translated text is: 在编译和执行测试之前,Maven 会编译主代码(所有这些类都是最新的,因为我们自上次编译以来没有做任何更改)。
如果您只想编译测试源代码(但不执行测试),可以执行以下操作:
mvn test-compile
现在您已经能够编译应用程序源代码、编译测试并执行测试,接下来您会想要进行下一步,因此您会问……
如何创建 JAR 并在本地仓库中安装它?
创建 JAR 文件足够简单,可以通过执行以下命令完成:
mvn package
您现在可以查看 ${project.basedir}/target
目录,您将看到生成的 JAR 文件。
现在您需要将您生成的工件(JAR 文件)安装到您的本地仓库中( ${user.home}/.m2/repository
是默认位置)。有关仓库的更多信息,您可以参考我们的《仓库简介》,但让我们继续安装我们的工件!为此,请执行以下命令:
mvn install
在执行此命令后,您应该看到以下输出:
[INFO] Scanning for projects... [INFO] [INFO] ----------------------< com.mycompany.app:my-app >---------------------- [INFO] Building my-app 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ my-app --- ... [INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ my-app --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ my-app --- ... [INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ my-app --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ my-app --- [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.mycompany.app.AppTest [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.025 s - in com.mycompany.app.AppTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] [INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ my-app --- [INFO] Building jar: <dir>/my-app/target/my-app-1.0-SNAPSHOT.jar [INFO] [INFO] --- maven-install-plugin:2.5.2:install (default-install) @ my-app --- [INFO] Installing <dir>/my-app/target/my-app-1.0-SNAPSHOT.jar to <local-repository>/com/mycompany/app/my-app/1.0-SNAPSHOT/my-app-1.0-SNAPSHOT.jar [INFO] Installing <dir>/my-app/pom.xml to <local-repository>/com/mycompany/app/my-app/1.0-SNAPSHOT/my-app-1.0-SNAPSHOT.pom [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1.678 s [INFO] Finished at: 2020-07-12T12:04:45+01:00 [INFO] ------------------------------------------------------------------------
请注意,确保插件(执行测试)寻找包含特定命名约定的文件中的测试。默认情况下,包含的测试有:
**/*Test.java
**/Test*.java
**/*TestCase.java
默认排除项包括:
**/Abstract*Test.java
**/Abstract*TestCase.java
您已经走过了设置、构建、测试、打包和安装典型 Maven 项目的流程。这可能是大多数项目将使用 Maven 所做的大部分工作。如果您注意到了,到目前为止您所做的一切都是由一个 18 行的文件驱动的,即项目的模型或 POM。如果您查看一个提供与我们迄今为止所达到相同功能的典型 Ant 构建文件,您会发现它的大小已经是 POM 的两倍,而我们才刚刚开始!在不修改我们目前 POM 的基础上,Maven 为您提供更多功能。要从我们的示例 Ant 构建文件中获得更多功能,您必须不断添加容易出错的代码。
所以你还能免费得到什么?有许多 Maven 插件可以与像我们上面那样的简单 POM 无缝工作。这里我们特别提一下,因为它被认为是 Maven 的宝贵特性之一:无需你做任何工作,这个 POM 就有足够的信息来生成你项目的网站!你很可能会想要自定义你的 Maven 网站,但如果你时间紧迫,你只需要执行以下命令来提供你项目的基本信息:
mvn site
存在许多其他独立的可执行目标,例如:
mvn clean
这将删除 target
目录以及所有构建数据,以确保在开始前它是全新的。
什么是快照版本?
请注意下面显示的 pom.xml
文件中版本标签的值具有后缀: -SNAPSHOT
。
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- ...
- <groupId>...</groupId>
- <artifactId>my-app</artifactId>
- ...
- <version>1.0-SNAPSHOT</version>
- <name>Maven Quick Start Archetype</name>
- ...
SNAPSHOT
值指的是开发分支上的“最新”代码,并不能保证代码是稳定的或不变的。相反,"发布"版本中的代码(任何不带后缀 SNAPSHOT
的版本值)是不变的。
换句话说,SNAPSHOT 版本是最终“发布”版本之前的“开发”版本。SNAPSHOT 版本比其发布版本“更早”。
在发布过程中,x.y-SNAPSHOT 版本变为 x.y。发布过程还将开发版本递增为 x.(y+1)-SNAPSHOT。例如,版本 1.0-SNAPSHOT 发布为版本 1.0,新的开发版本为版本 1.1-SNAPSHOT。
如何使用插件?
每当您想要自定义 Maven 项目的构建时,这通过添加或重新配置插件来完成。
为此示例,我们将配置 Java 编译器以允许使用 JDK 5.0 源代码。这就像在您的 POM 中添加以下内容一样简单:
- ...
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>3.3</version>
- <configuration>
- <source>1.5</source>
- <target>1.5</target>
- </configuration>
- </plugin>
- </plugins>
- </build>
- ...
您会注意到 Maven 中的所有插件看起来都像是一个依赖项——在某些方面确实如此。此插件将被自动下载并使用——包括您请求的特定版本(默认情况下使用最新可用的版本)。
configuration
元素将给定的参数应用于编译器插件中的每个目标。在上面的例子中,编译器插件已经作为构建过程的一部分被使用,这仅仅改变了配置。还可以向过程中添加新的目标,并配置特定的目标。有关信息,请参阅构建生命周期的简介。
为了了解插件可用的配置,您可以查看插件列表并导航到您正在使用的插件和目标。有关如何配置插件可用参数的一般信息,请参阅《插件配置指南》。
如何向我的 JAR 文件添加资源?
另一种常见用例,无需修改上述 POM 文件即可满足,即打包资源到 JAR 文件中。对于这个常见任务,Maven 再次依赖于标准目录布局,这意味着通过使用标准的 Maven 约定,您只需将这些资源放置在标准目录结构中,就可以简单地打包资源到 JAR 文件中。
您在下面的示例中可以看到,我们将目录 ${project.basedir}/src/main/resources
添加进去,我们将希望打包到 JAR 中的任何资源都放置在这个目录中。Maven 使用的简单规则是这样的:放置在 ${project.basedir}/src/main/resources
目录内的任何目录或文件都将按照与 JAR 根目录相同的结构打包到您的 JAR 中。
my-app |-- pom.xml `-- src |-- main | |-- java | | `-- com | | `-- mycompany | | `-- app | | `-- App.java | `-- resources | `-- META-INF | `-- application.properties `-- test `-- java `-- com `-- mycompany `-- app `-- AppTest.java
因此,您可以在我们的例子中看到,我们有一个 META-INF
目录,在该目录中有一个 application.properties
文件。如果您解压了 Maven 为您创建的 JAR 文件并查看它,您会看到以下内容:
|-- META-INF | |-- MANIFEST.MF | `-- application.properties | `-- maven | `-- com.mycompany.app | `-- my-app | |-- pom.properties | `-- pom.xml `-- com `-- mycompany `-- app `-- App.class
如您所见, ${project.basedir}/src/main/resources
的内容从 JAR 文件的底部开始,我们的 application.properties
文件就在 META-INF
目录中。您还会注意到那里还有一些其他文件,如 META-INF/MANIFEST.MF
以及 pom.xml
和 pom.properties
文件。这些文件是 Maven 生成 JAR 时标准配置的。如果您选择,可以创建自己的清单,但如果您不创建,Maven 会默认生成一个。(您也可以修改默认清单中的条目。我们稍后会讨论这个问题。) pom.xml
和 pom.properties
文件被打包进 JAR 中,这样 Maven 产生的每个工件都是自我描述的,并且如果需要,您还可以在自己的应用程序中利用元数据。一个简单的用途可能是检索您应用程序的版本。在 POM 文件上操作需要您使用一些 Maven 工具,但可以使用标准的 Java API 来利用属性,如下所示:
#Generated by Maven #Tue Oct 04 15:43:21 GMT-05:00 2005 version=1.0-SNAPSHOT groupId=com.mycompany.app artifactId=my-app
为了将资源添加到单元测试的类路径中,您遵循与添加资源到 JAR 相同的模式,只是放置资源的目录是 ${project.basedir}/src/test/resources
。此时,您的项目目录结构将如下所示:
my-app |-- pom.xml `-- src |-- main | |-- java | | `-- com | | `-- mycompany | | `-- app | | `-- App.java | `-- resources | `-- META-INF | |-- application.properties `-- test |-- java | `-- com | `-- mycompany | `-- app | `-- AppTest.java `-- resources `-- test.properties
在单元测试中,您可以使用以下简单的代码片段来访问测试所需的资源:
- ...
- // Retrieve resource
- InputStream is = getClass().getResourceAsStream( "/test.properties" );
- // Do something with the resource
- ...
如何过滤资源文件?
有时资源文件需要包含只能在构建时提供的值。在 Maven 中,使用语法 ${<property name>}
将包含该值的属性引用放入资源文件中。该属性可以是您在 pom.xml
中定义的值之一,用户 settings.xml
中定义的值,外部属性文件中定义的属性,或系统属性。
为了在复制时让 Maven 过滤资源,只需将 filtering
设置为 true,针对您的 pom.xml
中的资源目录:
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.mycompany.app</groupId>
- <artifactId>my-app</artifactId>
- <version>1.0-SNAPSHOT</version>
- <packaging>jar</packaging>
- <name>Maven Quick Start Archetype</name>
- <url>http://maven.apache.org</url>
- <dependencies>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.11</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
- <build>
- <resources>
- <resource>
- <directory>src/main/resources</directory>
- <filtering>true</filtering>
- </resource>
- </resources>
- </build>
- </project>
您会注意到我们不得不添加 build
、 resources
和 resource
这些之前不存在的元素。此外,我们还需要明确指出资源位于 src/main/resources
目录中。所有这些信息之前都作为默认值提供,但由于 filtering
的默认值为 false,我们不得不将其添加到我们的 pom.xml
中,以覆盖该默认值并将 filtering
设置为 true。
要引用在您的 pom.xml
中定义的属性,属性名称使用定义值的 XML 元素的名称,允许将“pom”用作项目(根)元素的别名。因此, ${project.name}
指的是项目的名称, ${project.version}
指的是项目的版本, ${project.build.finalName}
指的是打包构建的项目时创建的文件的最终名称等。请注意,POM 的一些元素有默认值,因此在这些值可用的情况下,不需要在您的 pom.xml
中明确定义。同样,用户 settings.xml
中的值可以使用以“settings”开头的属性名称进行引用(例如, ${settings.localRepository}
指的是用户本地仓库的路径)。
为了继续我们的例子,让我们向 application.properties
文件(我们将其放在 src/main/resources
目录中)添加几个属性,这些属性的值将在资源过滤时提供:
# application.properties application.name=${project.name} application.version=${project.version}
在设置好之后,您可以执行以下命令(process-resources 是构建生命周期阶段,其中资源被复制和过滤):
mvn process-resources
并且位于 target/classes
下的 application.properties
文件(最终将放入 jar 文件中)看起来是这样的:
# application.properties application.name=Maven Quick Start Archetype application.version=1.0-SNAPSHOT
要引用外部文件中定义的属性,您只需在您的 pom.xml
中添加对该外部文件的引用。首先,让我们创建我们的外部属性文件,并将其命名为 src/main/filters/filter.properties
:
# filter.properties my.filter.value=hello!
接下来,我们将在 pom.xml
中添加对该新文件的引用:
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.mycompany.app</groupId>
- <artifactId>my-app</artifactId>
- <version>1.0-SNAPSHOT</version>
- <packaging>jar</packaging>
- <name>Maven Quick Start Archetype</name>
- <url>http://maven.apache.org</url>
- <dependencies>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.11</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
- <build>
- <filters>
- <filter>src/main/filters/filter.properties</filter>
- </filters>
- <resources>
- <resource>
- <directory>src/main/resources</directory>
- <filtering>true</filtering>
- </resource>
- </resources>
- </build>
- </project>
然后,如果我们在这 application.properties
文件中添加对这个属性的引用:
# application.properties application.name=${project.name} application.version=${project.version} message=${my.filter.value}
下一次执行 mvn process-resources
命令将把我们的新属性值放入 application.properties
。作为在外部文件中定义 my.filter.value 属性的一种替代方法,您也可以在您的 pom.xml
的 properties
部分中定义它,您将得到相同的效果(注意,我同样不需要引用 src/main/filters/filter.properties
):
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.mycompany.app</groupId>
- <artifactId>my-app</artifactId>
- <version>1.0-SNAPSHOT</version>
- <packaging>jar</packaging>
- <name>Maven Quick Start Archetype</name>
- <url>http://maven.apache.org</url>
- <dependencies>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.11</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
- <build>
- <resources>
- <resource>
- <directory>src/main/resources</directory>
- <filtering>true</filtering>
- </resource>
- </resources>
- </build>
- <properties>
- <my.filter.value>hello</my.filter.value>
- </properties>
- </project>
过滤资源也可以从系统属性中获取值;无论是 Java 内置的系统属性(如 java.version
或 user.home
)还是使用标准 Java -D 参数在命令行上定义的属性。为了继续这个例子,让我们将我们的 application.properties
文件修改为如下所示:
# application.properties java.version=${java.version} command.line.prop=${command.line.prop}
现在,当你执行以下命令(注意在命令行上定义 command.line.prop 属性), application.properties
文件将包含系统属性的值。
mvn process-resources "-Dcommand.line.prop=hello again"
如何使用外部依赖项?
您可能已经注意到了我们在示例 POM 中使用的 dependencies
元素。实际上,您一直都在使用外部依赖,但在这里我们将更详细地讨论其工作原理。如需更全面的介绍,请参阅我们的《依赖机制简介》。
第 dependencies
节列出了我们的项目构建(无论它是否需要在编译时、测试时、运行时或任何其他时间)所需的所有外部依赖项。目前,我们的项目只依赖于 JUnit(为了清晰,我移除了所有资源过滤的内容):
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.mycompany.app</groupId>
- <artifactId>my-app</artifactId>
- <version>1.0-SNAPSHOT</version>
- <packaging>jar</packaging>
- <name>Maven Quick Start Archetype</name>
- <url>http://maven.apache.org</url>
- <dependencies>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.11</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
- </project>
对于每个外部依赖项,您至少需要定义以下 4 个方面:groupId、artifactId、version 和 scope。groupId、artifactId 和 version 与构建该依赖项的项目在 pom.xml
中给出的相同。scope 元素表示您的项目如何使用该依赖项,可以是 compile
、 test
和 runtime
等值。有关可以指定依赖项的所有信息的更多信息,请参阅项目描述符参考。
关于整体依赖机制的更多信息,请参阅《依赖机制导论》。
使用关于依赖项的这些信息,Maven 将能够在构建项目时引用该依赖项。Maven 从哪里引用依赖项?Maven 会查找您的本地仓库( ${user.home}/.m2/repository
是默认位置)以找到所有依赖项。在前面的一节中,我们将项目(my-app-1.0-SNAPSHOT.jar)的工件安装到本地仓库中。一旦安装在那里,另一个项目只需将其依赖项信息添加到其 pom.xml
中,就可以简单地引用那个 jar 作为依赖项:
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <groupId>com.mycompany.app</groupId>
- <artifactId>my-other-app</artifactId>
- ...
- <dependencies>
- ...
- <dependency>
- <groupId>com.mycompany.app</groupId>
- <artifactId>my-app</artifactId>
- <version>1.0-SNAPSHOT</version>
- <scope>compile</scope>
- </dependency>
- </dependencies>
- </project>
关于在其他地方构建的依赖项怎么办?它们是如何进入我的本地仓库的?每当一个项目引用了本地仓库中不可用的依赖项时,Maven 会从远程仓库下载该依赖项到本地仓库。你可能注意到,当你构建你的第一个项目时 Maven 下载了很多东西(这些下载是用于构建项目的各种插件的依赖项)。默认情况下,Maven 使用的远程仓库可以在 https://repo.maven.apache.org/maven2/ 找到(并浏览)。你也可以设置自己的远程仓库(可能是你公司的中央仓库)来代替或补充默认的远程仓库。有关仓库的更多信息,你可以参考《仓库简介》。
让我们给我们的项目添加另一个依赖项。假设我们已经给代码添加了一些日志记录,需要添加 log4j 作为依赖项。首先,我们需要知道 log4j 的 groupId、artifactId 和版本。在 Maven Central 上适当的目录被称为/maven2/log4j/log4j。在这个目录中有一个名为 maven-metadata.xml 的文件。以下是 log4j 的 maven-metadata.xml 文件的样子:
- <metadata>
- <groupId>log4j</groupId>
- <artifactId>log4j</artifactId>
- <version>1.1.3</version>
- <versioning>
- <versions>
- <version>1.1.3</version>
- <version>1.2.4</version>
- <version>1.2.5</version>
- <version>1.2.6</version>
- <version>1.2.7</version>
- <version>1.2.8</version>
- <version>1.2.11</version>
- <version>1.2.9</version>
- <version>1.2.12</version>
- </versions>
- </versioning>
- </metadata>
从该文件中,我们可以看到我们想要的 groupId 是"log4j",artifactId 也是"log4j"。我们看到有很多不同的版本值可供选择;目前,我们将使用最新版本,1.2.12(一些 maven-metadata.xml 文件也可能指定哪个版本是当前发布版本:参见仓库元数据参考)。在 maven-metadata.xml 文件旁边,我们可以看到对应于 log4j 库每个版本的目录。在这些目录中,我们将找到实际的 jar 文件(例如 log4j-1.2.12.jar)以及一个 pom 文件(这是依赖项的 pom.xml
,表示它可能具有的其他依赖项和其他信息)以及另一个 maven-metadata.xml 文件。还有对应于这些文件的 md5 文件,其中包含这些文件的 MD5 哈希值。您可以使用它来验证库或确定您可能已经使用的特定库的版本。
现在我们知道了所需的信息,可以将依赖项添加到我们的 pom.xml 中:
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.mycompany.app</groupId>
- <artifactId>my-app</artifactId>
- <version>1.0-SNAPSHOT</version>
- <packaging>jar</packaging>
- <name>Maven Quick Start Archetype</name>
- <url>http://maven.apache.org</url>
- <dependencies>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.11</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>log4j</groupId>
- <artifactId>log4j</artifactId>
- <version>1.2.12</version>
- <scope>compile</scope>
- </dependency>
- </dependencies>
- </project>
现在,当我们编译项目( mvn compile
)时,我们会看到 Maven 为我们下载 log4j 依赖项。
如何在我的远程仓库中部署我的 jar 文件?
为将 jar 包部署到外部仓库,您必须在 pom.xml
中配置仓库 URL,并在 settings.xml
中配置连接到仓库的认证信息。
这里是一个使用 SCP 和用户名/密码认证的示例:
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.mycompany.app</groupId>
- <artifactId>my-app</artifactId>
- <version>1.0-SNAPSHOT</version>
- <packaging>jar</packaging>
- <name>Maven Quick Start Archetype</name>
- <url>http://maven.apache.org</url>
- <dependencies>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.11</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.codehaus.plexus</groupId>
- <artifactId>plexus-utils</artifactId>
- <version>1.0.4</version>
- </dependency>
- </dependencies>
- <build>
- <filters>
- <filter>src/main/filters/filters.properties</filter>
- </filters>
- <resources>
- <resource>
- <directory>src/main/resources</directory>
- <filtering>true</filtering>
- </resource>
- </resources>
- </build>
- <!--
- |
- |
- |
- -->
- <distributionManagement>
- <repository>
- <id>mycompany-repository</id>
- <name>MyCompany Repository</name>
- <url>scp://repository.mycompany.com/repository/maven2</url>
- </repository>
- </distributionManagement>
- </project>
- <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
- ...
- <servers>
- <server>
- <id>mycompany-repository</id>
- <username>jvanzyl</username>
- <!-- Default value is ~/.ssh/id_dsa -->
- <privateKey>/path/to/identity</privateKey> (default is ~/.ssh/id_dsa)
- <passphrase>my_key_passphrase</passphrase>
- </server>
- </servers>
- ...
- </settings>
请注意,如果您连接到配置文件 sshd_config 中将“PasswordAuthentication”参数设置为“no”的 openssh ssh 服务器,您将需要在每次用户名/密码认证时输入您的密码(尽管您可以通过输入用户名和密码使用其他 ssh 客户端登录)。在这种情况下,您可能希望切换到公钥认证。
在使用密码时应当谨慎。更多信息,请参阅密码加密。
如何创建文档?
要让您快速开始使用 Maven 的文档系统,您可以使用原型机制通过以下命令为现有项目生成一个站点:
mvn archetype:generate \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DarchetypeArtifactId=maven-archetype-site \ -DgroupId=com.mycompany.app \ -DartifactId=my-app-site
现在前往创建网站的指南学习如何为您的项目创建文档。
如何构建其他类型的工程?
请注意,生命周期适用于任何项目类型。例如,在基本目录中,我们可以创建一个简单的 Web 应用程序:
mvn archetype:generate \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DarchetypeArtifactId=maven-archetype-webapp \ -DgroupId=com.mycompany.app \ -DartifactId=my-webapp
请注意,这些都必须在同一行上。这将创建一个名为 my-webapp
的目录,其中包含以下项目描述符:
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.mycompany.app</groupId>
- <artifactId>my-webapp</artifactId>
- <version>1.0-SNAPSHOT</version>
- <packaging>war</packaging>
- <dependencies>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.11</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
- <build>
- <finalName>my-webapp</finalName>
- </build>
- </project>
注意 <packaging>
元素——这告诉 Maven 以 WAR 方式构建。切换到 webapp 项目的目录并尝试:
mvn package
您将看到 target/my-webapp.war
已构建,并且所有常规步骤都已执行。
如何同时构建多个项目?
Maven 内置了处理多个模块的概念。在本节中,我们将展示如何一步构建上述 WAR 文件,并将之前的 JAR 文件也包含在内。
首先,我们需要在上面的目录中添加一个父 pom.xml
文件,所以它应该看起来像这样:
+- pom.xml +- my-app | +- pom.xml | +- src | +- main | +- java +- my-webapp | +- pom.xml | +- src | +- main | +- webapp
您创建的 POM 文件应包含以下内容:
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.mycompany.app</groupId>
- <artifactId>app</artifactId>
- <version>1.0-SNAPSHOT</version>
- <packaging>pom</packaging>
- <modules>
- <module>my-app</module>
- <module>my-webapp</module>
- </modules>
- </project>
我们需要从 webapp 依赖 JAR 文件,所以将其添加到 my-webapp/pom.xml
:
- ...
- <dependencies>
- <dependency>
- <groupId>com.mycompany.app</groupId>
- <artifactId>my-app</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
- ...
- </dependencies>
最后,将以下 <parent>
元素添加到子目录中的另外两个 pom.xml
文件中:
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <groupId>com.mycompany.app</groupId>
- <artifactId>app</artifactId>
- <version>1.0-SNAPSHOT</version>
- </parent>
- ...
现在,试试看...从顶级目录运行:
mvn verify
WAR 已创建在 my-webapp/target/my-webapp.war
,JAR 已包含:
$ jar tvf my-webapp/target/my-webapp-1.0-SNAPSHOT.war 0 Fri Jun 24 10:59:56 EST 2005 META-INF/ 222 Fri Jun 24 10:59:54 EST 2005 META-INF/MANIFEST.MF 0 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/ 0 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/com.mycompany.app/ 0 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/com.mycompany.app/my-webapp/ 3239 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/com.mycompany.app/my-webapp/pom.xml 0 Fri Jun 24 10:59:56 EST 2005 WEB-INF/ 215 Fri Jun 24 10:59:56 EST 2005 WEB-INF/web.xml 123 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/com.mycompany.app/my-webapp/pom.properties 52 Fri Jun 24 10:59:56 EST 2005 index.jsp 0 Fri Jun 24 10:59:56 EST 2005 WEB-INF/lib/ 2713 Fri Jun 24 10:59:56 EST 2005 WEB-INF/lib/my-app-1.0-SNAPSHOT.jar
这是如何工作的?首先,创建的父 POM(称为 app
),具有 pom
的打包方式和定义的模块列表。这告诉 Maven 在项目集上运行所有操作,而不是仅针对当前项目(要覆盖此行为,您可以使用 --non-recursive
命令行选项)。
接下来,我们告诉 WAR 需要 my-app
个 JAR。这有几个作用:它使 JAR 在 WAR 的类路径上对任何代码(在本例中无代码)可用,确保 JAR 总是在 WAR 之前构建,并指示 WAR 插件将其包含在其库目录中。
您可能已经注意到 junit-4.11.jar
是一个依赖项,但最终没有包含在 WAR 中。这是因为 <scope>test</scope>
元素——它仅用于测试,因此作为编译时依赖项 my-app
,它没有被包含在 Web 应用程序中。
最后一步是包含一个父定义。这确保了即使项目与其父项目分开分布,也可以通过在仓库中查找来始终定位到 POM。