1.简介

构建软件项目通常包括以下任务:下载依赖项,在类路径上放置其他jar,将源代码编译为二进制代码,运行测试,将编译后的代码打包为可部署的工件(例如JAR,WAR和ZIP文件),以及部署这些工件。到应用程序服务器或存储库。

Apache Maven可以自动执行这些任务,从而最大程度地减少了人为手动构建软件时出错的风险,并将编译和打包代码的工作与代码构建的工作分开。

在本文中,我们将探索这个功能强大的工具,该工具使用XML编写的配置信息(Project Object Model(POM))来描述,构建和管理Java软件项目。

2.为什么要使用Maven?

Maven的主要功能是:

  • 遵循最佳实践的简单项目设置: Maven尝试通过提供项目模板(命名为原型)来避免尽可能多的配置
  • 依赖关系管理:它包括自动更新,下载和验证兼容性,以及报告依赖关系关闭(也称为传递依赖关系)
  • 项目依赖项和插件之间的隔离:使用Maven,可从依赖项存储库中检索项目依赖项,而从插件存储库中检索任
  • 何插件的依赖项,从而在插件开始下载其他依赖项时减少冲突
  • 中央存储库系统:可以从本地文件系统或公共存储库(例如Maven Central)加载项目依赖项

3.POM

通过一个pom.xml文件表示的Project Object Model(POM)完成Maven项目的配置。该POM描述项目,管理的依赖,并配置插件构建的软件。

pom.xml还限定多模块项目模块之间的关系。

让我们看一下典型POM文件的基本结构:

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.ripjava</groupId>
    <artifactId>com.ripjava</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>test</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.7.0-M1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
            //...
            </plugin>
        </plugins>
    </build>
</project

让我们仔细看看这些构造。

3.1 项目标识符

Maven使用一组标识符(也称为坐标)来唯一的标识项目并指定如何打包项目工件:

  • groupId – 创建项目的公司或组的唯一基本名称
  • artifactId – 项目的唯一名称
  • version – 项目的版本
  • packaging – 包装方法(例如WAR / JAR / ZIP

其中的前三个(groupId:artifactId:version)组合起来形成唯一标识符,并且是您用来指定项目将使用哪些版本的外部库(例如JAR)的机制。

3.2 依赖关系

项目使用的这些外部库称为依赖项。

Maven中的依赖项管理功能可确保从中央存储库自动下载这些库,因此您不必将其存储在本地。

这是Maven的一项关键功能,具有以下优点:

  • 通过减少从远程存储库的下载次数,使用较少的存储空间
  • 使check out项目更快
  • 提供了一个有效的平台来交换组织内外的二进制工件,而无需每次都从源构建工件

为了声明对外部库的依赖,您需要提供groupId,artifactIdversion

让我们看一个例子:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>

当Maven处理依赖关系时,它将把spring-context库下载到本地Maven存储库中。

3.2 储存库(Repositories)

Maven中的存储库用于保存构建工件和各种类型的依赖项。

默认本地存储库位于用户主目录下的*.m2 / repository*文件夹中。

如果本地存储库中有jar包或插件,则Maven将使用它。否则,它将从中央存储库下载并存储在本地存储库中。默认的中央存储库是Maven Central

但是,某些库(例如JBoss服务器)在中央存储库中不可用,但在备用存储库中可用。对于这些库,您需要在pom.xml文件中提供指向备用存储库的URL 。

比如:

<repositories>
    <repository>
        <id>JBoss repository</id>
        <url>http://repository.jboss.org/nexus/content/groups/public/</url>
    </repository>
</repositories>

可以在项目中使用多个存储库。

3.4 属性

定制属性可以帮助使您的pom.xml文件更易于阅读和维护。

在经典用例中,可以使用自定义属性来定义项目依赖项的版本。

<properties>
    <spring.version>5.2.6.RELEASE</spring.version>
</properties>

<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>>${spring.version}</version>
 </dependency>

</dependencies>

现在,如果您想将Spring升级到更新的版本,只需更改属性标签内的值,并且使用该属性在其标签中的所有依赖项都将被更新。

当然,属性也经常用于定义构建路径变量。

比如:

<properties>
    <project.build.folder>${project.build.directory}/tmp/</project.build.folder>
</properties>
 
<plugin>
    //...
    <outputDirectory>${project.resources.build.folder}</outputDirectory>
    //...
</plugin>

3.5 构建

构建部分也是pom文件的一个非常重要的部分。它提供有关默认Maven 目标,已编译项目的目录以及应用程序最终名称的信息。

默认的构建部分如下所示:

<build>
    <defaultGoal>install</defaultGoal>
    <directory>${basedir}/target</directory>
    <finalName>${artifactId}-${version}</finalName>
    <filters>
      <filter>filters/filter1.properties</filter>
    </filters>
    //...
</build>

编译后的工件的默认输出文件夹名为target,打包后的工件的最终名称包含artifactIdversion,但是可以随时对其进行更改。

3.6 Profiles

Maven的另一个重要功能是它对配置文件的支持。profile基本上是一组配置值。通过使用配置文件,您可以针对不同的环境(例如生产/测试/开发)自定义构建:

<profiles>
    <profile>
        <id>production</id>
        <build>
            <plugins>
                <plugin>
                //...
                </plugin>
            </plugins>
        </build>
    </profile>
    <profile>
        <id>development</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <build>
            <plugins>
                <plugin>
                //...
                </plugin>
            </plugins>
        </build>
     </profile>
 </profiles>

如您在上面的示例中所见,默认配置文件设置为development

如果要运行生产环境配置,那么可以使用以下Maven命令:

mvn clean install -Pproduction

4. Maven构建生命周期

每个Maven构建都遵循指定的生命周期

您可以执行多个构建生命周期 目标,包括用于编译项目代码,创建程序包并将归档文件安装在本地Maven依赖项存储库中。

4.1 生命周期阶段

以下列表显示了最重要的Maven 生命周期阶段:

  • validate –检查项目的正确性
  • compile –将提供的源代码编译为二进制工件
  • test –执行单元测试
  • package –将编译后的代码打包到存档文件中
  • integration-test –执行其他测试,这些测试需要包装
  • verify –检查包装是否有效
  • install –将软件包文件安装到本地Maven存储库中
  • deploy –将软件包文件部署到远程服务器或存储库

4.2 插件和目标

Maven 插件是一个或多个目标的集合。目标是分阶段执行的,有助于确定目标的执行顺序。

Maven正式支持的丰富插件列表可在此处获得

要执行以上任何一个阶段,我们只需要调用一个命令:

	mvn <phase>

例如,mvn clean install将先前创建的jar / war / zip文件和已编译的类删除(*clean),*并执行安装新归档文件所需的所有阶段(install)

注意,插件提供的目标可以与生命周期的不同阶段进行关联。

5. 第一个Maven项目

在本节中,我们将使用Maven的命令行功能来创建Java项目。

5.1 生成一个简单的Java项目

为了构建一个简单的Java项目,让我们运行以下命令:

mvn archetype:generate
  -DgroupId=com.ripjava
  -DartifactId=com.ripjava.test
  -DarchetypeArtifactId=maven-archetype-quickstart
  -DinteractiveMode=false

groupId是指示创建了一个项目,这往往是公司域名的反转。

artifactId的是在项目中使用的基础包名称,而我们使用标准的原型。

由于我们未指定版本和包装类型,因此它们将被设置为默认值-版本将被设置为1.0-SNAPSHOT,包装将被设置为jar

如果您不知道要提供哪些参数,则可以始终指定InteractiveMode = true,以便Maven询问所有必需的参数。

输出如下

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:3.0.0:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:3.0.0:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO]
[INFO] --- maven-archetype-plugin:3.0.0:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Batch mode
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quickstart:1.0
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: basedir, Value: /Users/jundongpei/resource/ripjava/repo/tutorials/maven
[INFO] Parameter: package, Value: com.ripjava
[INFO] Parameter: groupId, Value: com.ripjava
[INFO] Parameter: artifactId, Value: com.ripjava.test
[INFO] Parameter: packageName, Value: com.ripjava
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] project created from Old (1.x) Archetype in dir: /xxx/resource/ripjava/repo/tutorials/maven/com.ripjava.test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  5.068 s
[INFO] Finished at: 2020-05-23T22:07:25+09:00
[INFO] ------------------------------------------------------------------------

命令完成后,我们在src / main / java文件夹中包含一个包含App.java类的Java项目,该类只是一个简单的“ Hello World”程序。

我们在src / test / java中也有一个示例测试类。

该项目的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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.ripjava</groupId>
  <artifactId>com.ripjava.test</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>com.ripjava.test</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>

默认情况下提供了junit依赖关系。不过我们需要将junit:3.8.1的依赖替换掉。

不然会出现如下的错误:

程序包junit.framework不存在
package junit.framework does not exist

替换之后如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.ripjava</groupId>
  <artifactId>com.ripjava.test</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>com.ripjava.test</name>
  <url>http://maven.apache.org</url>
  <dependencies>
      <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
      </dependency>
  </dependencies>
  <build>
    <sourceDirectory>src</sourceDirectory>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>11</source>
                <target>11</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>1.5.0</version>
            <configuration>
                <mainClass>com.ripjava.App</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>
</project>

5.2 编译和打包

下一步是编译项目:

mvn compile

Maven将贯穿编译阶段所需的所有生命周期阶段,以构建项目的源代码。

如果只想运行测试阶段,则可以使用:

mvn test

现在让我们调用package阶段*,*该阶段将代码编译打包成jar文件:

mvn package

5.3 执行

最后,我们将使用exec-maven-plugin执行我们的Java项目。

让我们在pom.xml中配置必要的插件:

<build>
    <sourceDirectory>src</sourceDirectory>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>11</source>
                <target>11</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>1.5.0</version>
            <configuration>
                <mainClass>com.ripjava.App</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>

第一个插件maven-compiler-plugin负责使用Java版本1.8编译源代码。

EXEC-Maven的插件里需要跑设置一下项目的mainClass

要执行该应用程序,我们运行以下命令:

mvn exec:java

6.多模块项目

Maven中处理多模块项目(也称为聚合器项目)的机制称为Reactor

Reactor收集所有可用的模块来构建,然后排序项目到正确的构建顺序逐一构建。

让我们看看如何创建一个多模块父项目。

6.1 创建父项目

首先,我们需要创建一个父项目。为了创建一个名称为parent-project的新项目,我们使用以下命令:

mvn archetype:generate -DarchetypeGroupId=org.codehaus.mojo.archetypes \
 -DarchetypeArtifactId=pom-root \
 -DarchetypeVersion=RELEASE \
 -DgroupId=com.ripjava \
 -DartifactId=parent-project \
 -DinteractiveMode=false

我们可以看到生成的pom文件中

<packaging>pom</packaging>

6.2 创建子模块项目

在下一步中,我们从parent-project目录创建子模块项目

cd parent-project
mvn archetype:generate -DgroupId=com.ripjava  \
        -DartifactId=core  \
        -DinteractiveMode=false
mvn archetype:generate -DgroupId=com.ripjava  \
        -DartifactId=service  \
        -DinteractiveMode=false
mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes  \
        -DarchetypeArtifactId=maven-archetype-webapp  \
        -DarchetypeVersion=RELEASE \
        -DgroupId=com.ripjava   \
        -DartifactId=webapp  \
        -DinteractiveMode=false

为了验证我们是否正确创建了子模块,请查看parent-project的pom.xml文件,在该文件中应该看到三个模块:

  <modules>
    <module>core</module>
    <module>service</module>
    <module>webapp</module>
  </modules>

此外,将在每个子模块的pom.xml中添加一个父部分:

  <parent>
    <groupId>com.ripjava</groupId>
    <artifactId>parent-project</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

6.3 在父项目中启用依赖性管理

依赖性管理是一种集中多模块父项目及其子项目的依赖性信息的机制。

当您拥有一组继承公共父项的项目或模块时,可以将有关依赖项的所有必需信息放入公共pom.xml文件中。

这将简化对子POM中的依赖的引用。

让我们看一下样本父级的pom.xml

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        //...
    </dependencies>
</dependencyManagement>

通过在父级中声明spring-core版本,所有依赖spring-core的子模块都可以仅使用groupIdartifactId声明依赖项,并且该版本将被继承:

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

此外,您可以在父级的pom.xml中提供依赖项管理的排除项,以使特定的库不会被子模块继承:

<exclusions>
    <exclusion>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
    </exclusion>
</exclusions>

最后,如果子模块需要使用托管依赖项的其他版本,则可以在子级的pom.xml文件中覆盖托管版本:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.2.1.RELEASE</version>
</dependency>

注意,虽然子模块从其父项目继承,但父项目不一定具有其聚合的任何模块。

另一方面,父项目也可以聚合不从其继承的项目。

有关继承和聚合的更多信息,请参考此文档

6.4 更新子模块并构建项目

我们可以更改每个子模块的打包类型。

例如,webapp的打包类型是war

<packaging>war</packaging>

现在,我们可以使用mvn clean install命令测试项目的构建。

Maven日志的输出应与此类似:

[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for parent-project 1.0-SNAPSHOT:
[INFO]
[INFO] parent-project ..................................... SUCCESS [  0.339 s]
[INFO] core ............................................... SUCCESS [  1.683 s]
[INFO] service ............................................ SUCCESS [  0.367 s]
[INFO] webapp Maven Webapp ................................ SUCCESS [  0.885 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

7. 总结

在本文中,我们讨论了Apache Maven构建工具的一些流行的功能。

与往常一样,可以在GitHub上获得代码示例。