Skip to content

Maven

Maven 简介

Maven 这个词可以翻译为“知识的积累”,也可以翻译为“专家”或“内行”。Maven 主要服务于基于 Java 平台的项目构建、依赖管理和项目信息管理。

  • Maven 是优秀的构建工具 - Maven 作为一个构建工具,不仅能帮我们自动化构建,还能够抽象构建过程,提供构建任务实现;它跨平台,对外提供了一致的操作接口,这一切足以使它成为优秀的、流行的构建工具。

  • Maven 不仅仅是构建工具 - Maven 不仅是构建工具,还是一个依赖管理工具和项目信息管理工具。它提供了中央仓库,能帮我们自动下载构件。

什么是 POM

POM(Project Object Model,项目对象模型)是 Maven 的基本工作单元。它是一个 XML 文件,包含 Maven 用于构建项目的项目信息和配置细节。

最小的 POM

POM 的最低要求如下:

  • project - POM 根标签
  • modelVersion - 应该设置为 4.0.0
  • groupId - 项目所在组的 ID
  • artifactId - 项目在组中的 ID
  • version - 项目在组中的版本

示例:

xml
<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1</version>
</project>

POM 需要配置 groupIdartifactIdversion。这三个值形成了项目的基本坐标。它的格式是<groupId>:<artifactId>:<version>。对于上面的例子,它的坐标是 com.mycompany.app:my-app:1

Maven 标准目录布局

Maven 默认约定了一套目录结构,常用的目录结构如下:

目录描述
src/main/javaApplication/Library sources
src/main/resourcesApplication/Library resources
src/main/webappWeb application sources
src/test/javaTest sources
src/test/resourcesTest resources
LICENSE.txtProject's license
NOTICE.txtNotices and attributions required by libraries that the project depends on
README.txtProject's readme

详情请查看:Introduction to the Standard Directory Layout

第一个 Maven 项目

下面开始创建一个名为 hello-maven 的 Maven 项目。

创建 hello-maven 目录

sh
$ mkdir hello-maven
$ cd hello-maven/

创建 pom.xml 文件

sh
$ touch pom.xml

pom.xml 内容如下:

xml
<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>study.helloworld.maven</groupId>
  <artifactId>hello-maven</artifactId>
  <version>1.0.0-SNAPSHOT</version>
</project>

编写主代码

sh
$ mkdir -p src/main/java
$ cd src/main/java/

$ mkdir -p study/helloworld/maven
$ cd study/helloworld/maven/
$ touch HelloMaven.java

HelloMaven.java 内容如下:

java
package study.helloworld.maven;

public class HelloMaven {
    public static void main(String[] args) {
        System.out.println(new HelloMaven().sayHello());
    }

    public String sayHello() {
        return "Hello, Maven!";
    }
}

编译主代码

切换到 pom.xml 所在的目录并执行 mvn compile 命令:

sh
$ mvn compile
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< study.helloworld.maven:hello-maven >--------------------
[INFO] Building hello-maven 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello-maven ---
[INFO] skip non existing resourceDirectory <dir>/hello-maven/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello-maven ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to <dir>/hello-maven/target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.754 s
[INFO] Finished at: 2022-06-10T16:14:24+08:00
[INFO] ------------------------------------------------------------------------

$ ls
pom.xml  src/  target/

从输出中可以看到,编译后的类被放在 ${basedir}/target/classes 中,这是 Maven 使用的另一个标准约定。

编写测试代码

JUnit 是一个 Java 编程语言的单元测试框架。要使用 JUnit,首先需要为 hello-maven 项目添加一个 JUnit 依赖,修改后的 pom.xml 的内容如下:

xml
<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>study.helloworld.maven</groupId>
  <artifactId>hello-maven</artifactId>
  <version>1.0.0-SNAPSHOT</version>

  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.8.2</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>
sh
$ mkdir -p src/test/java
$ cd src/test/java/

$ mkdir -p study/helloworld/maven
$ cd study/helloworld/maven/
$ touch HelloMavenTest.java

HelloMavenTest.java 内容如下:

java
package study.helloworld.maven;

import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;

public class HelloMavenTest {
    @Test
    public void testSayHello() {
        HelloMaven helloMaven = new HelloMaven();
        assertEquals.equals("Hello, Maven!", helloMaven.sayHello());
    }
}

编译测试代码并运行单元测试

切换到 pom.xml 所在的目录并执行 mvn test 命令:

sh
$ mvn test
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< study.helloworld.maven:hello-maven >--------------------
[INFO] Building hello-maven 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello-maven ---
[INFO] skip non existing resourceDirectory <dir>/hello-maven/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello-maven ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to <dir>/hello-maven/target/classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello-maven ---
[INFO] skip non existing resourceDirectory <dir>/hello-maven/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hello-maven ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to <dir>/hello-maven/target/test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello-maven ---
[INFO] Surefire report directory: <dir>/hello-maven/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running study.helloworld.maven.HelloMavenTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.868 s
[INFO] Finished at: 2022-06-14T15:57:49+08:00
[INFO] ------------------------------------------------------------------------

打包

切换到 pom.xml 所在的目录并执行 mvn package 命令:

sh
$ mvn package
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< study.helloworld.maven:hello-maven >--------------------
[INFO] Building hello-maven 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello-maven ---
[INFO] skip non existing resourceDirectory <dir>/hello-maven/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello-maven ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to <dir>/hello-maven/target/classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello-maven ---
[INFO] skip non existing resourceDirectory <dir>/hello-maven/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hello-maven ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to <dir>/hello-maven/target/test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello-maven ---
[INFO] Surefire report directory: <dir>/hello-maven/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running study.helloworld.maven.HelloMavenTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.023 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello-maven ---
[INFO] Building jar: <dir>/hello-maven/target/hello-maven-1.0.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.692 s
[INFO] Finished at: 2022-06-14T16:48:23+08:00
[INFO] ------------------------------------------------------------------------

现在可以查看 ${basedir}/target 目录,将看到生成的 JAR 文件。

运行

进入 ${basedir}/target 目录,执行以下命令:

sh
$ java -jar hello-maven-1.0.0-SNAPSHOT.jar
hello-maven-1.0.0-SNAPSHOT.jar中没有主清单属性

默认打包生成的 JAR 是不能够直接运行的,因为带有 main 方法的类信息不会添加到 manifest 中(打开 JAR 文件中的 META-INF/MANIFEST.MF 文件,将无法看到 Main-Class 一行)。

manifest
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: Pan
Created-By: Apache Maven 3.8.5
Build-Jdk: 1.8.0_31

为了生成可执行的 JAR 文件,需要借助 [maven-shade-plugin](https://maven.apache.org/plugins/maven-shade-plugin/) 插件,配置该插件如下:

xml
<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>study.helloworld.maven</groupId>
  <artifactId>hello-maven</artifactId>
  <version>1.0.0-SNAPSHOT</version>

  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.8.2</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <mainClass>study.helloworld.maven.HelloMaven</mainClass>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

重新打包后,再次运行。

sh
$ mvn package
$ java -jar hello-maven-1.0.0-SNAPSHOT.jar
Hello, Maven!

使用 Archetype 生成 Maven 项目骨架

Archetype 是一个 Maven 项目模板工具包。Archetype 将帮助用户创建 Maven 项目模板,并为用户提供生成这些项目模板参数化版本的方法。

要基于一个 Archetype 创建一个新项目,可以使用 mvn archetype:generate 命令。

sh
$ mvn archetype:generate
[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.2.1:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:3.2.1:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO]
[INFO] --- maven-archetype-plugin:3.2.1:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Interactive mode
[INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: remote -> am.ik.archetype:elm-spring-boot-blank-archetype (Blank multi project for Spring Boot + Elm)
2: remote -> am.ik.archetype:graalvm-blank-archetype (Blank project for GraalVM)
3: remote -> am.ik.archetype:graalvm-springmvc-blank-archetype (Blank project for GraalVM + Spring MVC)

...

3102: remote -> za.co.absa.hyperdrive:component-archetype_2.11 (-)
3103: remote -> za.co.absa.hyperdrive:component-archetype_2.12 (-)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 1914:

会列出许多 Archetype 供选择,直接回车会使用默认的 Archetype(org.apache.maven.archetypes:maven-archetype-quickstart)。

sh
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 1914:
Choose org.apache.maven.archetypes:maven-archetype-quickstart version:
1: 1.0-alpha-1
2: 1.0-alpha-2
3: 1.0-alpha-3
4: 1.0-alpha-4
5: 1.0
6: 1.1
7: 1.3
8: 1.4
Choose a number: 8:

直接回车会使用最新稳定版本的 Archetype 并提示输入 groupIdartifactIdversionpackage

sh
Define value for property 'groupId': study.helloworld.maven
Define value for property 'artifactId': hello-world
Define value for property 'version' 1.0-SNAPSHOT: :
Define value for property 'package' study.helloworld.maven: :
Confirm properties configuration:
groupId: study.helloworld.maven
artifactId: hello-world
version: 1.0-SNAPSHOT
package: study.helloworld.maven
 Y: :

确认无误后点击回车,Maven 项目的骨架已创建成功。

sh
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: maven-archetype-quickstart:1.4
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: study.helloworld.maven
[INFO] Parameter: artifactId, Value: hello-world
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: study.helloworld.maven
[INFO] Parameter: packageInPathFormat, Value: study/helloworld/maven
[INFO] Parameter: package, Value: study.helloworld.maven
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: groupId, Value: study.helloworld.maven
[INFO] Parameter: artifactId, Value: hello-world
[INFO] Project created from Archetype in dir: <dir>/hello-world
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:31 min
[INFO] Finished at: 2022-06-15T11:35:00+08:00
[INFO] ------------------------------------------------------------------------

项目的目录树结构如下:

sh
$ tree hello-world
hello-world
  pom.xml
  
└─src
    ├─main
  └─java
      └─study
          └─helloworld
              └─maven
                      App.java
                      
    └─test
        └─java
            └─study
                └─helloworld
                    └─maven
                            AppTest.java

开发自己的 Archetype

也许你所在组织的一些项目都使用同样的框架和项目结构,为一个个项目重复同样的配置及同样的目录结构显然是难以让人接受的。更好的做法是创建一个属于自己的 Archetype,这个 Archetype 包含了一些通用的 POM 配置、目录结构,甚至是 Java 类及资源文件,然后在创建项目的时候,就可以直接使用该 Archetype,并提供一些基本参数,如 groupIdartifactIdversionmaven-archetype-plugin 会处理其他原本需要手工处理的劳动。这样不仅节省了时间,也降低了错误配置发生的概率。

Archetype 项目的结构

一个典型的 Maven Archetype 项目主要包括如下几个部分:

  1. pom.xml - Archetype 自身的 POM。

  2. src/main/resources/META-INF/maven/archetype-metadata.xml - Archetype 的描述符文件。

  3. src/main/resources/archetype-resources/pom.xml - 基于该 Archetype 生成的项目的 POM 原型。

  4. src/main/resources/archetype-resources/** - 其他需要包含在 Archetype 中的内容。

sh
$ tree hello-maven-archetype
hello-maven-archetype
  pom.xml

└─src
    └─main
        └─resources
            ├─archetype-resources
  pom.xml

  └─src
      ├─main
  └─java
          App.java

      └─test
          └─java
                  AppTest.java

            └─META-INF
                └─maven
                        archetype-metadata.xml

可以使用 mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-archetype 快速创建一个 Maven Archetype 项目。

也可以按照以下步骤创建 Archetype:

为 Archetype 创建一个 pom.xml

首先,和任何其他 Maven 项目一样,Archetype 项目自身也需要有一个 POM。这个 POM 主要包含该 Archetype 的坐标信息,这样 Maven 才能定位并使用它。

xml
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>study.helloworld.maven</groupId>
  <artifactId>hello-maven-archetype</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>maven-archetype</packaging>

  <name>Archetype - hello-maven-archetype</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <build>
    <extensions>
      <extension>
        <groupId>org.apache.maven.archetype</groupId>
        <artifactId>archetype-packaging</artifactId>
        <version>3.2.1</version>
      </extension>
    </extensions>
  </build>
</project>

创建 Archetype 描述符文件

一个 Archetype 最核心的部分是 archetype-metadata.xml 描述符文件,它必须位于 src/main/resources/META-INF/maven/ 目录中。它主要用来控制两件事情:一是声明哪些目录及文件应该包含在 Archetype 中;二是这个 Archetype 使用哪些属性参数。

xml
<?xml version="1.0" encoding="UTF-8"?>

<archetype-descriptor xmlns="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-descriptor/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-descriptor/1.0.0 http://maven.apache.org/xsd/archetype-descriptor-1.0.0.xsd"
  name="${artifactId}">

  <fileSets>
    <fileSet filtered="true" packaged="true">
      <directory>src/main/java</directory>
    </fileSet>
    <fileSet filtered="true" packaged="true">
      <directory>src/test/java</directory>
    </fileSet>
  </fileSets>
  <requiredProperties>
  	<requiredProperty key="groupId">
  		<defaultValue>study.helloworld.hello</defaultValue>
  	</requiredProperty>
  	<requiredProperty key="echo" />
  </requiredProperties>
</archetype-descriptor>

<archetype-descriptor> 标签内的 name 属性应该和 Archetype 根目录下的 ${artifactId} 保持一致。

requiredPropertiesfileSetsmodules 标签代表了项目的不同部分:

  • requiredProperties - 从此 Archetype 生成项目所需的属性列表

  • fileSets - 文件集的定义

    fileSets 可以包含一个或者多个 fileSet 子元素,每个 fileSet 定义一个目录,以及与该目录相关的包含或排除规则。

    fileSet 有两个属性:

    • filtered 表示是否对该文件集合应用属性替换。例如,像 ${x} 这样的内容是否替换为命令行输入的 x 参数的值;

    • packaged 表示是否将该目录下的内容放到生成项目的包路径下。

  • modules - 模块定义

创建 Maven pom.xml 模板文件及其他文件集模板

要创建的 Archetype 的下一个部分是 pom.xml 模板,它位于 src/main/resources/archetype-resources/pom.xml。任何 pom.xml 都可以,只是不要忘记将 artifactIdgroupId 设置为变量 ${artifactId}${groupId}。在调用 archetype:generate 时,这两个变量都将从命令行中初始化。

xml
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>${groupId}</groupId>
  <artifactId>${artifactId}</artifactId>
  <version>${version}</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

安装 Archetype 并运行

  • 安装

    sh
    $ mvn install
  • 运行

    sh
    $ mvn archetype:generate -DarchetypeCatalog=local -DarchetypeGroupId=study.helloworld.maven -DarchetypeArtifactId=hello-maven-archetype -DarchetypeVersion=1.0-SNAPSHOT

使用 Maven 私服仓库

从私服下载

通过 setting.xml 文件配置(全局)

xml
<mirrors>
  <mirror>
      <!-- 该镜像的唯一标识符 -->
      <id>maven-public</id>
      <!-- 镜像名称 -->
      <name>maven-public</name>
      <!-- 该镜像的 URL。构建系统会优先考虑使用该 URL,而非使用默认的服务器 URL。 -->
      <url>http://localhost:8081/repository/maven-public/</url>     
      <!-- 指的是访问任何仓库都使用我们的私服 -->
      <mirrorOf>*</mirrorOf>
  </mirror>
</mirrors>

可能还需要配置用户名和密码。

xml
<servers>
  <server>
    <!-- repository/mirror 的 ID -->
    <id>maven-public</id>
    <username>admin</username>
    <password>admin</password>
  </server>
</servers>

通过 pom.xml 文件配置(项目)

  • 依赖
xml
<repositories>
  <repository>
    <id>maven-public</id>
    <name>maven-public</name>
    <url>http://localhost:8081/repository/maven-public/</url>
    <!-- 正式版本 -->
    <releases>
      <!-- 是否启用:true/false -->
      <enabled>true</enabled>
      <!-- 更新策略:always/daily(默认)/interval:X(X 是以分钟为单位的整数)/never -->
      <updatePolicy>never</updatePolicy>
      <!-- 校验策略:ignore/fail/warn -->
      <checksumPolicy>warn</checksumPolicy>
    </releases>
    <!-- 快照版本 -->
    <snapshots>
      <enabled>true</enabled>
      <updatePolicy>always</updatePolicy>
      <checksumPolicy>fail</checksumPolicy>
    </snapshots>
  </repository>
</repositories>
  • 插件
xml
<pluginRepositories>
  <pluginRepository>
    <id>maven-public</id>
    <name>maven-public</name>
    <url>http://localhost:8081/repository/maven-public/</url>
    <!-- 正式版本 -->
    <releases>
      <!-- 是否启用:true/false -->
      <enabled>true</enabled>
      <!-- 更新策略:always/daily(默认)/interval:X(X 是以分钟为单位的整数)/never -->
      <updatePolicy>never</updatePolicy>
      <!-- 校验策略:ignore/fail/warn -->
      <checksumPolicy>warn</checksumPolicy>
    </releases>
    <!-- 快照版本 -->
    <snapshots>
      <enabled>true</enabled>
      <updatePolicy>always</updatePolicy>
      <checksumPolicy>fail</checksumPolicy>
    </snapshots>
  </pluginRepository>
</pluginRepositories>

发布到私服

xml
<distributionManagement>
  <repository>
    <id>maven-releases</id>
    <name>maven-releases</name>
    <url>http://localhost:8081/repository/maven-releases/</url>
  </repository>
  <snapshotRepository>
    <id>maven-snapshots</id>
    <name>maven-snapshots</name>
    <url>http://localhost:8081/repository/maven-snapshots/</url>
  </snapshotRepository>
</distributionManagement>

可能还需要在 setting.xml 文件配置用户名和密码。

xml
<servers>
  <server>
    <id>maven-releases</id>
    <username>admin</username>
    <password>admin</password>
  </server>
  <server>
    <id>maven-snapshots</id>
    <username>admin</username>
    <password>admin</password>
  </server>
</servers>

Maven 属性

Maven 属性的值可以在 POM 中的任何地方使用符号 ${X} 访问,其中 X 是属性。

或者它们也可以被插件用作默认值,例如:

xml
<project>
  ...
  <properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 
  </properties>
  ...
</project>

它们有五种不同的风格:

  1. env.X - 所有环境变量都可以使用以 env. 开头的 Maven 属性引用。例如 ${env.JAVA_HOME} 指代了 JAVA_HOME 环境变量的值。使用 mvn help:system 可以查看所有的环境变量。

  2. project.x - 所有 POM 元素都可以使用以 project. 开头的 Maven 属性引用。例如 <project><version>1.0</version></project> 可以通过 ${project.version} 访问。

常用的 POM 属性包括:

属性描述
${project.basedir}项目的根目录
${project.build.sourceDirectory}项目的主源码目录,默认为 src/main/java/
${project.build.testSourceDirectory}项目的测试源码目录,默认为 src/test/java/
${project.build.directory}项目构建输出目录,默认为 target/
${project.outputDirectory}项目主代码编译输出目录,默认为 target/classes/
${project.testOutputDirectory}项目测试代码编译输出目录,默认为 target/test-classes/
${project.groupId}项目的 groupId
${project.artifactId}项目的 artifactId
${project.version}项目的 version
${project.build.finalName}项目打包输出文件的名称,默认为 ${project.artifactId}-${project.version}
  1. settings.x - 与 POM 属性同理,所有 settings.xml 文件中的 XML 元素都可以使用以 settings. 开头的 Maven 属性引用。例如 <settings><localRepository>~/.m2/repository</localRepository></settings> 可以通过 ${settings.localRepository} 访问。

  2. Java 系统属性 - 通过 java.lang.System.getProperties() 访问的所有属性都可以作为 Maven 属性使用,例如 ${java.home} 指向了用户目录。使用 mvn help:system 可以查看所有的 Java 系统属性。

  3. 自定义属性 - 用户可以在POM的 <properties> 元素下自定义 Maven 属性。例如 <properties><hello.world>study</hello.world></properties> 可以通过 ${hello.world} 访问。

Maven 属性使用示例

xml
<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>study.helloworld.maven</groupId>
  <artifactId>hello-maven</artifactId>
  <version>1.0.0-SNAPSHOT</version>

  <properties>
    <hello.world>study</hello.world>
  </properties>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>3.1.0</version>
        <executions>
          <execution>
            <phase>clean</phase>
            <configuration>
              <target>
                <echo level="info" message="env.JAVA_HOME: ${env.JAVA_HOME}"/>
                <echo level="info" message="project.basedir: ${project.basedir}"/>
                <echo level="info" message="project.version: ${project.version}"/>
                <echo level="info" message="settings.localRepository: ${settings.localRepository}"/>
                <echo level="info" message="java.home: ${java.home}"/>
                <echo level="info" message="hello.world: ${hello.world}"/>
              </target>
            </configuration>
            <goals>
              <goal>run</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

输出结果。

sh
$ mvn clean
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------< study.helloworld.maven:hello-maven >-----------------
[INFO] Building hello-maven 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ hello-maven ---
[INFO] Deleting <dir>/hello-maven/target
[INFO]
[INFO] --- maven-antrun-plugin:3.1.0:run (default) @ hello-maven ---
[INFO] Executing tasks
[INFO]      [echo] env.JAVA_HOME: ~/soft/jdk1.8
[INFO]      [echo] project.basedir: <dir>/hello-maven
[INFO]      [echo] project.version: 1.0.0-SNAPSHOT
[INFO]      [echo] settings.localRepository: ~/.m2/repository
[INFO]      [echo] java.home: ~/soft/jdk1.8/jre
[INFO]      [echo] hello.world: study
[INFO] Executed tasks
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.085 s
[INFO] Finished at: 2022-06-30T10:33:08+08:00
[INFO] ------------------------------------------------------------------------

在资源中使用 Maven 属性

例如,有一个资源文件 src/main/resources/hello.yml,内容为:

yml
env.JAVA_HOME: ${env.JAVA_HOME}
project.basedir: ${project.basedir}
project.version: ${project.version}
settings.localRepository: ${settings.localRepository}
java.home: ${java.home}
hello.world: ${hello.world}

使用 mvn resources:resources 命令生成 target/classes/hello.yml 文件。会发现文件内容没有任何变化,变量并没有被替换。

加入以下配置:

xml
<project>
  ...
  <build>
    ...
    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
      </resource>
      ...
    </resources>
    ...
  </build>
  ...
</project>

再次使用 mvn resources:resources 命令生成 target/classes/hello.yml 文件。会发现文件内容有了变化,变量已被替换。

yml
env.JAVA_HOME: ~/soft/jdk1.8
project.basedir: <dir>/hello-maven
project.version: 1.0.0-SNAPSHOT
settings.localRepository: ~/.m2/repository
java.home: ~/soft/jdk1.8/jre
hello.world: study

此外,还可以通过命令行使用 -D 选项来赋值。例如,要将变量名 hello.world 的值改为“helloworld.study”,可以简单地调用以下命令:

xml
$ mvn resources:resources -Dhello.world=helloworld.study

Maven 常用插件

maven-shade-plugin

打包可执行的 JAR 文件

要创建一个可执行的 JAR,你只需要设置应用程序入口点的主类。

xml
<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <mainClass>study.helloworld.maven.HelloMaven</mainClass>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

createDependencyReducedPom

标记是否为阴影工件生成简化的POM。如果设置为true,则包含在优JAR中的依赖项将从生成的POM的<dependencies>部分中删除。简化后的POM将被命名为dependency-reduced-pom.xml,并存储在与阴影构件相同的目录中。除非你还指定了dependencyReducedPomLocation,否则插件将在项目的basedir中创建一个名为dependency-reduced-pom.xml的临时文件。

设置<createDependencyReducedPom>false</createDependencyReducedPom>表示不生成dependency-reduced-pom.xml临时文件。

maven-source-plugin

安装到仓库时附带源代码

我们在这里使用 package 阶段,因为它是在 install 阶段之前的阶段,从而确保在安装发生之前已经创建了源 JAR。

xml
<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-source-plugin</artifactId>
        <version>3.2.0</version>
        <executions>
          <execution>
            <id>attach-sources</id>
            <phase>package</phase>
            <goals>
              <goal>jar-no-fork</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

maven-surefire-plugin

跳过测试

不管怎样,我们总会要求 Maven 跳过测试,这很简单,在命令行加入参数 skipTests 就可以了。

sh
$ mvn clean package -DskipTests
...

[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hello-maven ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to <dir>/hello-maven/target/test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello-maven ---
[INFO] Tests are skipped.
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello-maven ---
[INFO] Building jar: <dir>/hello-maven/target/hello-maven-1.0.0-SNAPSHOT.jar

...

Maven 输出会告诉你它跳过了测试。

当然,也可以在 POM 中配置 maven-surefire-plugin 插件来提供该属性。但这是不推荐的做法,如果配置 POM 让项目长时间地跳过测试,则还要测试代码做什么呢?

xml
<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.12.4</version>
        <configuration>
          <skipTests>true</skipTests>
        </configuration>
      </plugin>
    </plugins>
  </build>
  ...
</project>

还可以在命令行使用 maven.test.skip 参数跳过测试代码的编译。

sh
$ mvn clean package -Dmaven.test.skip=true
...

[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hello-maven ---
[INFO] Not compiling test sources
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello-maven ---
[INFO] Tests are skipped.
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello-maven ---
[INFO] Building jar: <dir>/hello-maven/target/hello-maven-1.0.0-SNAPSHOT.jar

...

参数 maven.test.skip 同时控制了 maven-compiler-pluginmaven-surefire-plugin 两个插件的行为,测试代码编译跳过了,测试运行也跳过了。

xml
<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.10.1</version>
        <configuration>
          <skip>true</skip>
        </configuration>
      </plugin>
      
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.12.4</version>
        <configuration>
          <skipTests>true</skipTests>
        </configuration>
      </plugin>
    </plugins>
  </build>
  ...
</project>

maven-antrun-plugin

这个插件提供了在 Maven 中运行 Ant 任务的能力。您甚至可以将 Ant 脚本嵌入到 POM 中!

xml
<project>
  ...
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>3.1.0</version>
        <executions>
          <execution>
            <phase> <!-- a lifecycle phase --> </phase>
            <configuration>
              <target>
 
                <!--
                  Place any Ant task here. You can add anything
                  you can add between <target> and </target> in a
                  build.xml.
                -->
 
              </target>
            </configuration>
            <goals>
              <goal>run</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

引用 Maven Classpaths

xml
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-antrun-plugin</artifactId>
  <version>3.1.0</version>
  <executions>
    <execution>
      <phase>clean</phase>
      <configuration>
        <target>
          <property name="compile_classpath" refid="maven.compile.classpath"/>
          <property name="runtime_classpath" refid="maven.runtime.classpath"/>
          <property name="test_classpath" refid="maven.test.classpath"/>
          <property name="plugin_classpath" refid="maven.plugin.classpath"/>

          <echo message="compile classpath: ${compile_classpath}"/>
          <echo message="runtime classpath: ${runtime_classpath}"/>
          <echo message="test classpath:    ${test_classpath}"/>
          <echo message="plugin classpath:  ${plugin_classpath}"/>
        </target>
      </configuration>
      <goals>
        <goal>run</goal>
      </goals>
    </execution>
  </executions>
</plugin>
sh
$ mvn clean
...

[INFO] --- maven-antrun-plugin:3.1.0:run (default) @ hello-maven ---
[INFO] Executing tasks
[WARNING]      [echo] compile classpath: <dir>/hello-maven/target/classes
[WARNING]      [echo] runtime classpath: <dir>/hello-maven/target/classes
[WARNING]      [echo] test classpath:    <dir>/hello-maven/target/test-classes;<dir>/hello-maven/target/classes;~/.m2/repository/org/junit/jupiter/junit-jupiter-api/5.8.2/junit-jupiter-api-5.8.2.jar;~/.m2/repository/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar;~/.m2/repository/org/junit/platform/junit-platform-commons/1.8.2/junit-platform-commons-1.8.2.jar;~/.m2/repository/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar
[WARNING]      [echo] plugin classpath:  ~/.m2/repository/org/apache/maven/plugins/maven-antrun-plugin/3.1.0/maven-antrun-plugin-3.1.0.jar;~/.m2/repository/org/codehaus/plexus/plexus-utils/3.4.1/plexus-utils-3.4.1.jar;~/.m2/repository/org/apache/ant/ant/1.10.12/ant-1.10.12.jar;~/.m2/repository/org/apache/ant/ant-launcher/1.10.12/ant-launcher-1.10.12.jar;~/soft/jdk1.8/lib/tools.jar
[INFO] Executed tasks

...

打印 Maven 属性

xml
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-antrun-plugin</artifactId>
  <version>3.1.0</version>
  <executions>
    <execution>
      <phase>clean</phase>
      <configuration>
        <target>
          <echo level="info" message="项目根目录 project.basedir: ${project.basedir}"/>
          <echo level="info">项目版本 project.version: ${project.version}</echo>
        </target>
      </configuration>
      <goals>
        <goal>run</goal>
      </goals>
    </execution>
  </executions>
</plugin>
sh
$ mvn clean
...

[INFO] --- maven-antrun-plugin:3.1.0:run (default) @ hello-maven ---
[INFO] Executing tasks
[INFO]      [echo] 项目根目录 project.basedir: <dir>/hello-maven
[INFO]      [echo] 项目版本 project.version: 1.0.0-SNAPSHOT
[INFO] Executed tasks

...

开发自己的 Maven 插件

Maven 的任何行为都是由插件完成的,包括项目的清理、编译、测试以及打包等操作都有其对应的 Maven 插件。每个插件拥有一个或者多个目标,用户可以直接从命令行运行这些插件目标(Mojo),或者选择将目标绑定到 Maven 的生命周期。

如果我们有非常特殊的需求,并且无法找到现成的插件可供使用,那么就只能自己编写 Maven 插件了。

插件命名约定

强烈建议将你的插件命名为 <yourplugin>-maven-plugin

称它为 maven-<yourplugin>-plugin(注意“maven”是在插件名称的开头)是非常不鼓励的,因为它是由 Apache Maven 团队使用 groupIdorg.apache.maven.plugins 维护的官方 Apache Maven 插件的保留命名模式。使用这种命名模式是对 Apache Maven 商标的侵犯。

编写 Maven 插件的一般步骤

  1. 创建一个 packagingmaven-plugin 的项目。可以使用 mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-plugin 快速创建一个 Maven 插件项目。

  2. 为插件编写目标。每个插件都必须包含一个或者多个目标,Maven 称之为 Mojo(与 POJO 对应,后者指 Plain Old Java Object,这里指 Maven Old Java Object)。编写插件的时候必须提供一个或者多个继承自 AbstractMojo 的类。

  3. 为目标提供配置点:大部分 Maven 插件及其目标都是可配置的,因此在编写 Mojo 的时候需要注意提供可配置的参数。

  4. 编写代码实现目标行为:根据实际的需要实现 Mojo

  5. 错误处理及日志:当 Mojo 发生异常时,根据情况控制 Maven 的运行状态。在代码中编写必要的日志以便为用户提供足够的信息。

  6. 测试插件:编写自动化的测试代码测试行为,然后再实际运行插件以验证其行为。

第一个 Maven 插件

  1. 新建 Maven 项目,pom.xml 内容为:
xml
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>study.helloworld.maven</groupId>
  <artifactId>hello-maven-plugin</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>maven-plugin</packaging>

  <name>hello-maven-plugin Maven Plugin</name>

  <prerequisites>
    <maven>${maven.version}</maven>
  </prerequisites>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <maven.version>3.3.9</maven.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-plugin-api</artifactId>
      <version>${maven.version}</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.maven.plugin-tools</groupId>
      <artifactId>maven-plugin-annotations</artifactId>
      <version>3.6.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
</project>

TIP

  1. packaging 必须为 maven-plugin

  2. 可以看到一个 artifactIdmaven-plugin-api 的依赖,该依赖中包含了插件开发所必需的类,例如稍后会看到的 AbstractMojo

  1. 编写 Mojo 类:
java
package study.helloworld.maven;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;

@Mojo(name = "hello")
public class HelloMojo extends AbstractMojo {

  @Override
  public void execute() throws MojoExecutionException, MojoFailureException {
    getLog().info("Hello, Maven plugin!");
  }

}

TIP

  1. 每个插件目标类,或者说 Mojo,都必须继承 AbstractMojo 并实现 execute() 方法,只有这样 Maven 才能识别该插件目标,并执行 execute() 方法中的行为。

  2. 任何一个 Mojo 都必须使用 @Mojo 注解写明自己的目标名称,有了目标定义之后,才能在项目中配置该插件目标,或者在命令行调用之。

  1. 使用 mvn clean install 命令将该插件项目构建并安装到本地仓库后,就能使用它了。
sh
$ mvn study.helloworld.maven:hello-maven-plugin:1.0-SNAPSHOT:hello
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------< study.helloworld.maven:hello-maven >-----------------
[INFO] Building hello-maven 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- hello-maven-plugin:1.0-SNAPSHOT:hello (default-cli) @ hello-maven ---
[INFO] Hello, Maven plugin!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.200 s
[INFO] Finished at: 2022-07-30T09:19:42+08:00
[INFO] ------------------------------------------------------------------------

将 Mojo 附加到构建生命周期中

xml
<build>
  <plugins>
    <plugin>
      <groupId>study.helloworld.maven</groupId>
      <artifactId>hello-maven-plugin</artifactId>
      <version>1.0-SNAPSHOT</version>
      <executions>
        <execution>
          <phase>compile</phase>
          <goals>
            <goal>hello</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
sh
$ mvn compile
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------< study.helloworld.maven:hello-maven >-----------------
[INFO] Building hello-maven 1.0.0-SNAPSHOT

...

[INFO] --- maven-compiler-plugin:3.10.1:compile (default-compile) @ hello-maven ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to <dir>/hello-maven/target/classes
[INFO]
[INFO] --- hello-maven-plugin:1.0-SNAPSHOT:hello (default) @ hello-maven ---
[INFO] Hello, Maven plugin!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  4.808 s
[INFO] Finished at: 2022-07-30T09:22:54+08:00
[INFO] ------------------------------------------------------------------------

Released under the MIT License.