2.2 Maven 插件开发 2.2 Maven 插件开发 Maven 的强大之处在于其插件机制。插件是 Maven 的核心组成部分,Maven 的几乎所有构建、报告和管理任务都由插件完成。掌握 Maven 插件开发,意味着你可以根据项目需求扩展 Maven 的功能,定制构建流程,自动化各种任务,从而更高效地管理和构建项目。 本章节将深入探讨 Maven 插件开发,从基础概念到实战案例,帮助你理解插件的工作原理,学会编写自定义插件,并将其应用到实际项目中。 2.2.1 插件概述 2.2.1.1 什么是 Maven 插件? 简单来说,Maven 插件是扩展 Maven 功能的组件。它们是包含一个或多个 Mojo (Maven plain Old Java Object) 的 JAR 文件。
Maven 的强大之处在于其插件机制。插件是 Maven 的核心组成部分,Maven 的几乎所有构建、报告和管理任务都由插件完成。掌握 Maven 插件开发,意味着你可以根据项目需求扩展 Maven 的功能,定制构建流程,自动化各种任务,从而更高效地管理和构建项目。
本章节将深入探讨 Maven 插件开发,从基础概念到实战案例,帮助你理解插件的工作原理,学会编写自定义插件,并将其应用到实际项目中。
简单来说,Maven 插件是扩展 Maven 功能的组件。它们是包含一个或多个 Mojo (Maven plain Old Java Object) 的 JAR 文件。Mojo 是 Maven 插件中的可执行单元,每个 Mojo 代表一个特定的构建任务或目标(Goal)。
例如,maven-compiler-plugin 插件的 compile Mojo 负责编译 Java 源代码,maven-jar-plugin 插件的 jar Mojo 负责将编译后的类文件打包成 JAR 文件。
Maven 插件主要分为两种类型:
构建插件 (Build Plugins): 在构建生命周期中执行,用于执行构建任务,例如编译代码、打包、测试、部署等。我们常用的 maven-compiler-plugin、maven-jar-plugin、maven-surefire-plugin 等都属于构建插件。
报告插件 (Reporting Plugins): 在报告生命周期中执行,用于生成项目报告,例如项目站点、代码质量报告、依赖关系报告等。maven-project-info-reports-plugin、maven-surefire-report-plugin 等都属于报告插件。
本章节主要关注 构建插件 的开发。
Maven 插件的工作原理与 Maven 的生命周期紧密相关。当 Maven 执行一个构建生命周期阶段时,它会查找绑定到该阶段的插件目标 (Mojo)。然后,Maven 会按照配置顺序执行这些 Mojo。
每个 Mojo 都定义了其执行的目标 (Goal),以及需要的参数。Maven 会负责解析和注入这些参数,然后调用 Mojo 的 execute() 方法来执行具体的任务。
JDK 安装: 确保安装了 JDK 1.8 或更高版本。
Maven 安装: 确保安装了 Maven 3.x 或更高版本。
IDE (可选): 推荐使用 IntelliJ IDEA 或 Eclipse 等 IDE,它们对 Maven 项目有良好的支持。
Maven 提供了 maven-archetype-plugin 插件用于快速创建插件项目骨架。打开命令行终端,执行以下命令:
mvn archetype:generate \ -DgroupId=com.example \ -DartifactId=my-maven-plugin \ -DarchetypeArtifactId=maven-archetype-plugin \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DinteractiveMode=false
这个命令会创建一个名为 my-maven-plugin 的 Maven 项目,其目录结构如下:
my-maven-plugin/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/ │ │ │ └── MyMojo.java // 默认的 Mojo 示例 │ │ └── resources/ │ └── test/ │ └── java/ │ └── com/example/ │ └── MyMojoTest.java // 默认的 Mojo 测试示例 ├── pom.xml └── README.txt
src/main/java: 存放插件的 Java 源代码,Mojo 的实现代码就在这里。
src/main/resources: 存放插件的资源文件,例如 plugin.xml 插件描述符(虽然现在更推荐使用注解)。
src/test/java: 存放插件的测试代码。
pom.xml: 插件项目的 Maven POM 文件,定义了插件的元数据、依赖和构建配置。
打开 my-maven-plugin/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.example</groupId> <artifactId>my-maven-plugin</artifactId> <version>1.0-SNAPSHOT</version> <packaging>maven-plugin</packaging> <!-- 打包类型为 maven-plugin --> <name>my-maven-plugin Maven Plugin</name> <dependencies> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-plugin-api</artifactId> <version>3.0</version> <!-- Maven 插件 API 版本,根据你的 Maven 版本选择 --> </dependency> <dependency> <groupId>org.apache.maven.plugin-tools</groupId> <artifactId>maven-plugin-annotations</artifactId> <version>3.4</version> <!-- Maven 插件注解版本 --> <scope>provided</scope> <!-- 运行时不需要,编译时需要 --> </dependency> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-core</artifactId> <version>3.0</version> <!-- Maven Core 版本,根据你的 Maven 版本选择 --> </dependency> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-compat</artifactId> <version>3.0</version> <!-- Maven Compat 版本,根据你的 Maven 版本选择 --> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-plugin-plugin</artifactId> <version>3.6.0</version> <!-- Maven Plugin Plugin 版本 --> <configuration> <!-- see http://maven.apache.org/ref/current/maven-plugin-api/descriptor.html#annotations --> <goalPrefix>my-plugin</goalPrefix> <!-- 插件 Goal 前缀 --> <!-- 支持生成 plugin.xml 描述符,但现在更推荐使用注解 --> <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound> </configuration> <executions> <execution> <id>default-descriptor</id> <phase>process-classes</phase> <goals> <goal>descriptor</goal> <!-- 生成插件描述符 --> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <!-- Maven Compiler Plugin 版本 --> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
关键配置项解释:
<packaging>maven-plugin</packaging>: 指定项目打包类型为 maven-plugin,这表明这是一个 Maven 插件项目。
<dependencies>:
maven-plugin-api: Maven 插件 API,插件开发必须依赖的接口和类。
maven-plugin-annotations: Maven 插件注解,用于简化插件描述符的配置。
maven-core 和 maven-compat: Maven 核心和兼容性库,插件可能需要访问 Maven 的内部 API。
junit: 单元测试库。
<build><plugins><plugin>maven-plugin-plugin</plugin>: maven-plugin-plugin 是用于构建 Maven 插件的关键插件。
<goalPrefix>: 定义插件 Goal 的前缀,用户在使用插件时需要使用这个前缀,例如 my-plugin:my-goal。
<executions><execution><goals><goal>descriptor</goal></goals></execution></executions>: 配置 maven-plugin-plugin 的 descriptor Goal,用于生成插件描述符 plugin.xml。虽然现在更推荐使用注解,但了解描述符的生成过程仍然有帮助。
<skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>: 忽略找不到描述符的错误,因为我们主要使用注解方式。
打开 src/main/java/com/example/MyMojo.java 文件,默认的代码示例如下:
package com.example; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; /** * Goal which touches a timestamp file. * * @goal touch * * @phase process-sources */ @Mojo(name = "touch", defaultPhase = "process-sources") public class MyMojo extends AbstractMojo { /** * Location of the file. */ @Parameter(defaultValue = "${project.build.directory}", property = "outputDir", required = true) private String outputDirectory; public void execute() throws MojoExecutionException { getLog().info( "Hello, world." ); } }
代码解释:
package com.example;: 包名与 pom.xml 中定义的 <groupId> 和 <artifactId> 保持一致。
import ...;: 导入需要的类,包括 AbstractMojo 基类、MojoExecutionException 异常类和注解类。
@Mojo(...): Mojo 注解,用于声明这是一个 Mojo。
name = "touch": 定义 Mojo 的 Goal 名称为 touch。用户可以使用 my-plugin:touch 命令执行这个 Mojo。
defaultPhase = "process-sources": 将 Mojo 绑定到 process-sources 生命周期阶段。当 Maven 执行到 process-sources 阶段时,会自动执行这个 Mojo。
public class MyMojo extends AbstractMojo: Mojo 类 MyMojo 继承自 AbstractMojo 基类,AbstractMojo 提供了 Mojo 的基本功能和 API。
@Parameter(...): Parameter 注解,用于声明 Mojo 的参数。
defaultValue = "${project.build.directory}": 参数的默认值,这里使用了 Maven 属性 ${project.build.directory},表示项目构建输出目录。
property = "outputDir": 参数的 Maven 属性名,用户可以通过 -DoutputDir=xxx 在命令行设置这个参数的值。
required = true: 参数是否是必需的。
private String outputDirectory;: 参数的字段,用于存储参数的值。
public void execute() throws MojoExecutionException: execute() 方法 是 Mojo 的核心方法,Maven 在执行 Mojo 时会调用这个方法。
getLog().info( "Hello, world." );: 使用 getLog() 方法获取 Maven 日志对象,并输出 "Hello, world." 信息。在 my-maven-plugin 项目根目录下,打开命令行终端,执行以下命令:
mvn clean install
mvn clean: 清理项目,删除之前编译的类文件和生成的 JAR 包。
mvn install: 编译项目,打包成 JAR 包,并将插件安装到本地 Maven 仓库(默认是 ~/.m2/repository)。
安装成功后,你会在本地 Maven 仓库中找到 my-maven-plugin-1.0-SNAPSHOT.jar 文件。
要使用自定义插件,需要在另一个 Maven 项目的 pom.xml 文件中配置插件。创建一个新的 Maven 项目,例如 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.example</groupId> <artifactId>my-app</artifactId> <version>1.0-SNAPSHOT</version> <build> <plugins> <plugin> <groupId>com.example</groupId> <artifactId>my-maven-plugin</artifactId> <version>1.0-SNAPSHOT</version> <executions> <execution> <id>touch-goal</id> <phase>compile</phase> <!-- 将插件绑定到 compile 阶段 --> <goals> <goal>touch</goal> <!-- 执行 touch Goal --> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
配置解释:
<plugin>: 声明要使用的插件。
<groupId>com.example</groupId> 和 <artifactId>my-maven-plugin</artifactId> 和 <version>1.0-SNAPSHOT</version>: 指定插件的坐标,与插件项目 pom.xml 中定义的坐标一致。
<executions>: 配置插件的执行。
<execution>: 配置一个插件执行。
<id>touch-goal</id>: 执行的 ID,可以自定义。
<phase>compile</phase>: 将插件绑定到 compile 生命周期阶段。
<goals>: 指定要执行的插件 Goal。
<goal>touch</goal>: 执行 my-maven-plugin 插件的 touch Goal。在 my-app 项目根目录下,打开命令行终端,执行以下命令:
mvn compile
你会在 Maven 构建日志中看到 "Hello, world." 信息,这表明你的自定义插件已经成功执行。
在 Mojo 中,可以通过注入 Maven 的组件来获取 Maven 上下文信息,例如:
@Component ProjectBuilder projectBuilder;: 获取 ProjectBuilder 组件,用于构建 Maven 项目模型。
@Component MavenProject project;: 获取 MavenProject 组件,表示当前 Maven 项目。
@Component MavenSession session;: 获取 MavenSession 组件,表示当前的 Maven 会话。
@Component PluginDescriptorBuilder pluginDescriptorBuilder;: 获取 PluginDescriptorBuilder 组件,用于构建插件描述符。
@Component RepositorySystem repositorySystem;: 获取 RepositorySystem 组件,用于访问 Maven 仓库。
@Component ArtifactFactory artifactFactory;: 获取 ArtifactFactory 组件,用于创建 Maven 构件。
@Component ArtifactResolver artifactResolver;: 获取 ArtifactResolver 组件,用于解析 Maven 构件。
@Component ArtifactInstaller artifactInstaller;: 获取 ArtifactInstaller 组件,用于安装 Maven 构件到本地仓库。
@Component ArtifactDeployer artifactDeployer;: 获取 ArtifactDeployer 组件,用于部署 Maven 构件到远程仓库。
示例代码 (获取 MavenProject 和 MavenSession):
package com.example; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.project.MavenProject; import org.apache.maven.execution.MavenSession; /** * Goal which prints project information. * * @goal project-info * @phase process-sources */ @Mojo(name = "project-info", defaultPhase = "process-sources") public class ProjectInfoMojo extends AbstractMojo { @Component private MavenProject project; @Component private MavenSession session; public void execute() throws MojoExecutionException { getLog().info( "Project ArtifactId: " + project.getArtifactId() ); getLog().info( "Maven Base Directory: " + session.getExecutionRootDirectory() ); } }
使用示例:
将上述代码替换 MyMojo.java 的内容。
修改 pom.xml 中插件的 goal 为 project-info。
在 my-app 项目中配置使用 project-info Goal。
执行 mvn compile 命令。
你会在日志中看到项目 ArtifactId 和 Maven 根目录信息。
除了使用 @Parameter 注解配置参数外,还可以使用更丰富的参数配置选项:
property: 指定参数的 Maven 属性名,用户可以通过 -Dproperty=value 在命令行设置参数值。
defaultValue: 指定参数的默认值,可以是字符串、数字、布尔值或 Maven 属性。
required: 指定参数是否是必需的,默认为 false。
readonly: 指定参数是否是只读的,默认为 false。
expression: 使用表达式语言 (Expression Language) 获取参数值,例如 ${project.build.directory}、${settings.localRepository} 等。
alias: 为参数设置别名,方便用户使用。
since: 指定参数从哪个 Maven 版本开始可用。
示例代码 (更丰富的参数配置):
package com.example; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; /** * Goal which prints a custom message. * * @goal message * @phase process-sources */ @Mojo(name = "message", defaultPhase = "process-sources") public class MessageMojo extends AbstractMojo { /** * The message to display. */ @Parameter(property = "my-plugin.message", defaultValue = "Hello, Maven Plugin!", required = true, alias = "msg") private String message; /** * The number of times to repeat the message. */ @Parameter(property = "my-plugin.repeat", defaultValue = "1", readonly = false) private int repeatCount; public void execute() throws MojoExecutionException { for (int i = 0; i < repeatCount; i++) { getLog().info( "Message: " + message ); } } }
使用示例:
将上述代码替换 MyMojo.java 的内容。
修改 pom.xml 中插件的 goal 为 message。
在 my-app 项目中配置使用 message Goal。
执行以下命令,可以自定义消息和重复次数:
mvn my-plugin:message -Dmy-plugin.message="Custom Message" -Dmy-plugin.repeat=3 mvn my-plugin:message -Dmsg="Short Alias Message" -Dmy-plugin.repeat=2
Mojo 可以绑定到 Maven 的不同生命周期阶段,例如 process-sources、compile、package、install、deploy 等。通过 @Mojo 注解的 defaultPhase 属性可以指定 Mojo 的默认绑定阶段。
如果没有指定 defaultPhase,则 Mojo 不会默认绑定到任何生命周期阶段,需要用户显式地在 pom.xml 中配置执行阶段。
示例代码 (绑定到 package 阶段):
package com.example; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; /** * Goal which executes during package phase. * * @goal package-task * @phase package */ @Mojo(name = "package-task", defaultPhase = "package") public class PackageTaskMojo extends AbstractMojo { public void execute() throws MojoExecutionException { getLog().info( "Executing during package phase." ); } }
使用示例:
将上述代码替换 MyMojo.java 的内容。
修改 pom.xml 中插件的 goal 为 package-task。
在 my-app 项目中配置使用 package-task Goal。
执行 mvn package 命令,你会在 package 阶段看到插件的输出。
@Mojo 注解的 requiresDirectInvocation 和 requiresProject 属性可以控制 Mojo 的执行模式:
requiresDirectInvocation = true: 指定 Mojo 只能通过直接调用执行,不能通过生命周期阶段触发。默认为 false。
requiresProject = false: 指定 Mojo 是否需要在 Maven 项目上下文中执行,如果设置为 false,则可以在没有 pom.xml 的目录下执行 Mojo。默认为 true。
Mojo 可以依赖其他 Java 库或 Maven 组件。在插件项目的 pom.xml 文件中添加 <dependencies> 即可声明 Mojo 的依赖。
示例代码 (依赖 commons-io 库):
在 my-maven-plugin/pom.xml 文件中添加 commons-io 依赖:
<dependencies> </dependencies> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.11.0</version> </dependency> </dependencies>
在 Mojo 中使用 commons-io 库:
package com.example; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; /** * Goal which creates a directory. * * @goal create-dir * @phase process-sources */ @Mojo(name = "create-dir", defaultPhase = "process-sources") public class CreateDirMojo extends AbstractMojo { public void execute() throws MojoExecutionException { File dir = new File("target/my-dir"); try { FileUtils.forceMkdir(dir); getLog().info( "Directory created: " + dir.getAbsolutePath() ); } catch (IOException e) { throw new MojoExecutionException( "Failed to create directory", e ); } } }
使用示例:
将上述代码替换 MyMojo.java 的内容。
修改 pom.xml 中插件的 goal 为 create-dir。
在 my-app 项目中配置使用 create-dir Goal。
执行 mvn compile 命令,你会在 target 目录下看到 my-dir 目录被创建。
对 Maven 插件进行单元测试和集成测试非常重要,可以保证插件的质量和稳定性。
可以使用 JUnit 等单元测试框架对 Mojo 的逻辑进行单元测试。在 src/test/java 目录下编写测试用例,测试 Mojo 的各个方法和功能。
可以使用 maven-plugin-testing-harness 框架进行集成测试。这个框架提供了模拟 Maven 环境的功能,可以测试 Mojo 在 Maven 生命周期中的行为。
示例代码 (集成测试):
在 my-maven-plugin/pom.xml 文件中添加 maven-plugin-testing-harness 依赖:
<dependencies> </dependencies> <dependency> <groupId>org.apache.maven.plugin-testing</groupId> <artifactId>maven-plugin-testing-harness</artifactId> <version>3.3.0</version> <scope>test</scope> </dependency> </dependencies>