Appearance
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
- 项目所在组的 IDartifactId
- 项目在组中的 IDversion
- 项目在组中的版本
示例:
xml
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1</version>
</project>
POM 需要配置 groupId
、artifactId
和 version
。这三个值形成了项目的基本坐标。它的格式是<groupId>:<artifactId>:<version>
。对于上面的例子,它的坐标是 com.mycompany.app:my-app:1
。
Maven 标准目录布局
Maven 默认约定了一套目录结构,常用的目录结构如下:
目录 | 描述 |
---|---|
src/main/java | Application/Library sources |
src/main/resources | Application/Library resources |
src/main/webapp | Web application sources |
src/test/java | Test sources |
src/test/resources | Test resources |
LICENSE.txt | Project's license |
NOTICE.txt | Notices and attributions required by libraries that the project depends on |
README.txt | Project'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 并提示输入 groupId
、artifactId
、version
和 package
。
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,并提供一些基本参数,如 groupId
、artifactId
、version
,maven-archetype-plugin
会处理其他原本需要手工处理的劳动。这样不仅节省了时间,也降低了错误配置发生的概率。
Archetype 项目的结构
一个典型的 Maven Archetype 项目主要包括如下几个部分:
pom.xml - Archetype 自身的 POM。
src/main/resources/META-INF/maven/archetype-metadata.xml - Archetype 的描述符文件。
src/main/resources/archetype-resources/pom.xml - 基于该 Archetype 生成的项目的 POM 原型。
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}
保持一致。
requiredProperties
、fileSets
和 modules
标签代表了项目的不同部分:
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
都可以,只是不要忘记将 artifactId
和 groupId
设置为变量 ${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>
它们有五种不同的风格:
env.X
- 所有环境变量都可以使用以env.
开头的 Maven 属性引用。例如${env.JAVA_HOME}
指代了JAVA_HOME
环境变量的值。使用mvn help:system
可以查看所有的环境变量。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} |
settings.x
- 与 POM 属性同理,所有settings.xml
文件中的 XML 元素都可以使用以settings.
开头的 Maven 属性引用。例如<settings><localRepository>~/.m2/repository</localRepository></settings>
可以通过${settings.localRepository}
访问。Java 系统属性 - 通过
java.lang.System.getProperties()
访问的所有属性都可以作为 Maven 属性使用,例如${java.home}
指向了用户目录。使用mvn help:system
可以查看所有的 Java 系统属性。自定义属性 - 用户可以在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-plugin
和 maven-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 团队使用 groupId
为 org.apache.maven.plugins
维护的官方 Apache Maven 插件的保留命名模式。使用这种命名模式是对 Apache Maven 商标的侵犯。
编写 Maven 插件的一般步骤
创建一个
packaging
为maven-plugin
的项目。可以使用mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-plugin
快速创建一个 Maven 插件项目。为插件编写目标。每个插件都必须包含一个或者多个目标,Maven 称之为
Mojo
(与POJO
对应,后者指 Plain Old Java Object,这里指 Maven Old Java Object)。编写插件的时候必须提供一个或者多个继承自AbstractMojo
的类。为目标提供配置点:大部分 Maven 插件及其目标都是可配置的,因此在编写
Mojo
的时候需要注意提供可配置的参数。编写代码实现目标行为:根据实际的需要实现
Mojo
。错误处理及日志:当
Mojo
发生异常时,根据情况控制 Maven 的运行状态。在代码中编写必要的日志以便为用户提供足够的信息。测试插件:编写自动化的测试代码测试行为,然后再实际运行插件以验证其行为。
第一个 Maven 插件
- 新建 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
packaging
必须为maven-plugin
。可以看到一个
artifactId
为maven-plugin-api
的依赖,该依赖中包含了插件开发所必需的类,例如稍后会看到的AbstractMojo
。
- 编写
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
每个插件目标类,或者说
Mojo
,都必须继承AbstractMojo
并实现execute()
方法,只有这样 Maven 才能识别该插件目标,并执行execute()
方法中的行为。任何一个
Mojo
都必须使用@Mojo
注解写明自己的目标名称,有了目标定义之后,才能在项目中配置该插件目标,或者在命令行调用之。
- 使用
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] ------------------------------------------------------------------------