Maven插件开发
Maven本身只是一套框架,它的功能基于全部依赖于插件来实现。因此,掌握插件开发深度定制Maven的必修课。
插件本身也是Maven构件,构件标识的命名约定是 <yourplugin>-maven-plugin。不要命名为 maven-<yourplugin>-plugin,这种命名模式由官方插件(组标识 org.apache.maven.plugins)保留。
每个Maven插件包含一或多个Mojo,每个Mojo实现了一个插件目标(goal),Mojo通常编写为Java类。插件就是一系列Mojo的集合。
插件必须在自己的JAR包的 META-INF/maven/plugin.xml中提供描述符信息,你不需要手工编写描述符,使用Maven插件工具注解即可自动生成。
本章我们编写一个最简单的插件,它不需要参数,仅仅简单的打印一段消息。
可以将mojo理解为插件的一个目标(goal)的实现类。你需要使用@Mojo注解来声明一个类是mojo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package sample.plugin; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; @Mojo( name = "sayhi") public class GreetingMojo extends AbstractMojo { public void execute() throws MojoExecutionException { getLog().info( "Hello, world." ); } } |
抽象类AbstractMojo包含了实现mojo所需的基础代码,我们仅仅需要实现execute方法就可以了。execute方法可以抛出两种异常:
- MojoExecutionException:表示意外错误发生,控制台会显示BUILD ERROR消息
- MojoFailureException:表示非意外的错误发生,例如编译是白。控制台会显示BUILD FAILURE消息
插件本身也是Maven项目,因此你需要编写POM,示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<project> <modelVersion>4.0.0</modelVersion> <groupId>sample.plugin</groupId> <artifactId>hello-maven-plugin</artifactId> <version>1.0-SNAPSHOT</version> <!-- 打包方式必须指定为maven-plugin --> <packaging>maven-plugin</packaging> <name>Sample Parameter-less Maven Plugin</name> <dependencies> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-plugin-api</artifactId> <version>3.0</version> </dependency> <!-- 插件注解 --> <dependency> <groupId>org.apache.maven.plugin-tools</groupId> <artifactId>maven-plugin-annotations</artifactId> <version>3.4</version> <scope>provided</scope> </dependency> </dependencies> </project> |
另创建一个Maven项目,添加以下配置:
1 2 3 4 5 6 7 8 9 |
<build> <plugins> <plugin> <groupId>sample.plugin</groupId> <artifactId>hello-maven-plugin</artifactId> <version>1.0-SNAPSHOT</version> </plugin> </plugins> </build> |
然后调用我们的插件:
1 2 |
# mvn groupId:artifactId:version:goal mvn sample.plugin:hello-maven-plugin:1.0-SNAPSHOT:sayhi |
上面调用插件的mvn命令参数非常冗长,缩短的方式有几种:
- 如果希望执行本地仓库中,插件的最新版本,则version可以省略
- 可以为插件分配快捷前缀,如果插件命名遵循了 ${prefix}-maven-plugin格式则prefix自动为前缀。调用命令可以简化为 mvn sample.plugin:hello:sayhi
- 可以将组标识添加到Maven的settings.xml的pluginGroups,这样就可以进一步省略组标识:
123<pluginGroups><pluginGroup>sample.plugin</pluginGroup></pluginGroups>调用命令简化为 mvn hello:sayhi
在目标项目中增加如下配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<build> <plugins> <plugin> <groupId>sample.plugin</groupId> <artifactId>hello-maven-plugin</artifactId> <version>1.0-SNAPSHOT</version> <executions> <execution> <phase>compile</phase> <goals> <goal>sayhi</goal> </goals> </execution> </executions> </plugin> </plugins> </build> |
则在构建生命周期的compile阶段,sayhi目标自动执行。
你可以从原型创建插件项目,避免手写样板代码:
1 2 3 4 5 |
mvn archetype:generate \ -DgroupId=sample.plugin \ -DartifactId=hello-maven-plugin \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DarchetypeArtifactId=maven-archetype-plugin |
Mojo可以支持参数,目标项目的POM中可以声明传入的参数值,你也可以在命令行中以-D系统属性的风格传入参数值。
要定义Mojo参数,只需要为Mojo类的字段添加注解:
1 2 |
@Parameter( property = "sayhi.greeting", defaultValue = "Hello World!" ) private String greeting; |
上面的代码定义了一个参数greeting,并给定了默认值。此参数对应的系统属性为sayhi.greeting。
参数值可以使用 ${project.version}这样的表达式,来引用目标项目的属性。
1 2 3 4 5 6 7 8 9 |
<plugin> <groupId>sample.plugin</groupId> <artifactId>hello-maven-plugin</artifactId> <version>1.0-SNAPSHOT</version> <configuration> <!-- 这里使用参数的字段名而非系统属性 --> <greeting>Welcome</greeting> </configuration> </plugin> |
在插件参数表达式中,你可以使用以下对象:
对象 | 说明 |
session | 当前 MavenSession对象 |
session.* | |
localRepository | MavenSession.getLocalRepository() |
reactorProjects | MavenSession.getProjects() |
repositorySystemSession | MavenSession.getRepositorySession() |
project | MavenSession.getCurrentProject() |
project.* | |
pom.* | |
executedProject | MavenProject.getExecutionProject() |
settings | MavenSession.getSettings() |
settings.* | |
basedir |
MavenSession.getExecutionRootDirectory(),为空则取 System.getProperty( "user.dir" ) |
mojoExecution | 当前MojoExecution对象 |
mojo | |
mojo.* | |
plugin | MojoExecution.getMojoDescriptor().getPluginDescriptor() |
plugin.* | |
* | 还可以直接使用系统属性、项目属性 |
配置中的文本转换为Mojo参数字段的规则如下:
参数字段类型 | 文本表示 |
布尔型 | true或者false |
整数 | 包括byte, Byte, int, Integer, long, Long, short, Short,支持负号 |
浮点数 | 包括double, Double, float, Float,支持科学计数法 |
java.util.Date |
支持格式: yyyy-MM-dd HH:mm:ss.S a 例如2005-10-06 2:22:55.1 PM |
java.io.File | 文件系统路径,例如c:\temp |
java.net.URL | URL,例如https://gmem.cc |
Mojo字段可以使用数组、集合类:
1 2 |
@Parameter private String[] myArray; |
对应POM配置示例:
1 2 3 4 |
<myArray> <param>value1</param> <param>value2</param> </myArray> |
映射类型的字段POM配置示例,参数名为myMap:
1 2 3 4 |
<myMap> <key1>value1</key1> <key2>value2</key2> </myMap> |
Properties类型的POM配置示例,参数名为myProperties:
1 2 3 4 5 6 7 8 9 10 |
<myProperties> <property> <name>propertyName1</name> <value>propertyValue1</value> <property> <property> <name>propertyName2</name> <value>propertyValue2</value> <property> </myProperties> |
Mojo参数字段也可以是任意对象类型:
1 2 |
@Parameter private MyObject myObject; |
对应POM配置示例:
1 2 3 |
<myObject> <myField>test</myField> </myObject> |
用于测试单个Mojo的功能,并仿冒Maven的其它部分。
你可以像编写普通Junit测试用例那样,进行Mojo单元测试。但是,大部分情况下Mojo都需要被注入一个Maven项目的引用,可以 extends PlexusTestCase达成变量注入的目的。
此工具专门用于测试 org.apache.maven.reporting.AbstractMavenReport#execute()
在一个真实的Maven Build中测试Mojo,你需要提供一个测试用的Maven项目,插件需要安装到本地仓库。
Maven验证器为你的JUnit测试用例提供以下功能:
- 启动Maven
- 根据构建的输出、日志信息进行断言
- 从src/test/resources目录抽取出一个Maven项目,并存放到临时工作目录中,针对此项目进行测试
- 操控本地仓库
Maven项目本身也在用此验证器进行集成测试,下面是一些说明如何使用该验证器的代码片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class TrivialMavenVerifierTest extends TestCase { public void testMyPlugin() throws Exception { // 从/src/test/resources/中抽取测试用的Maven项目 File testDir = ResourceExtractor.simpleExtractResources( getClass(), "/my-dummy-maven-project" ); Verifier verifier; // 首选需要保证,此test项目产生的构件已经从本地仓库移除 verifier = new Verifier( testDir.getAbsolutePath() ); verifier.deleteArtifact( "org.apache.maven.its.itsample", "parent", "1.0", "pom" ); verifier.deleteArtifact( "org.apache.maven.its.itsample", "checkstyle-test", "1.0", "jar" ); verifier.deleteArtifact( "org.apache.maven.its.itsample", "checkstyle-assembly", "1.0", "jar" ); // 执行Maven命令 List cliOptions = new ArrayList(); cliOptions.add( "-N" ); verifier.executeGoal( "install" ); // 验证上述命令是否成功 verifier.verifyErrorFreeLog(); // 清理,准备下一个命令的执行 verifier.resetStreams(); } } |
这是一个插件,可以调用Maven,并提供一些BeanShell或Groovy的测试脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-invoker-plugin</artifactId> <version>1.10</version> <configuration> <projectsDirectory>src/it</projectsDirectory> <pomIncludes> <pomInclude>**/pom.xml</pomInclude> </pomIncludes> <postBuildHookScript>verify</postBuildHookScript> </configuration> <executions> <execution> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> ... </plugins> </build> |
要调试Maven插件,可以使用远程调试的方式。
你需要用 mvnDebug而非mvn来调用Maven,此命令默认会监听8000端口,等待Debugger的连接。
示例:
1 2 3 4 5 |
mvnDebug cc.gmem.yun.maven.plugins:maven-archetype-plugin:3.1.1:create-from-project \ -Darchetype.properties=archetype.properties -Darchetype.postPhase=package -Darchetype.partialArchetype=true # Preparing to execute Maven in debug mode # Listening for transport dt_socket at address: 8000 |
配合Intellij IDEA等开发工具,可以很容易实现本地调试。
在你的插件项目中,创建一个Maven的运行配置,设置环境变量:
1 |
MAVEN_DEBUG_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" |
然后Maven的Command line调用你的插件的Mojo,配置好了点击调试按钮即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecution; import org.apache.maven.plugin.descriptor.PluginDescriptor; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Execute; import org.apache.maven.plugins.annotations.InstantiationStrategy; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apache.maven.settings.Settings; // 此Mojo对应的目标的名称 @Mojo( name = "<goal-name>", aggregator = <false|true>, configurator = "<role hint>", // 执行策略 executionStrategy = "<once-per-session|always>", inheritByDefault = <true|false>, // 实例化策略 instantiationStrategy = InstantiationStrategy.<strategy>, // 如果用户没有在POM中明确设置此Mojo绑定到的phase,那么绑定一个MojoExecution到那个phase defaultPhase = LifecyclePhase.<phase>, requiresDependencyResolution = ResolutionScope.<scope>, requiresDependencyCollection = ResolutionScope.<scope>, // 提示此Mojo需要被直接调用(而非绑定到生命周期阶段) requiresDirectInvocation = <false|true>, // 提示此Mojo不能在离线模式下运行 requiresOnline = <false|true>, // 提示此Mojo必须在一个Maven项目内运行 requiresProject = <true|false>, // 提示此Mojo是否线程安全,线程安全的Mojo支持在并行构建中被并发的调用 threadSafe = <false|true> ) // (since Maven 3.0) // 何时执行此Mojo @Execute( goal = "<goal-name>", // 如果提供goal,则隔离执行此Mojo phase = LifecyclePhase.<phase>, // 在此生命周期阶段自动执行此Mojo lifecycle = "<lifecycle-id>" ) // 在此生命周期中执行此Mojo public class MyMojo extends AbstractMojo { @Parameter( name = "parameter", // 在POM中可使用别名来配置参数 alias = "myAlias", property = "a.property", defaultValue = "an expression, possibly with ${variables}", readonly = <false|true>, required = <false|true> ) private String parameter; @Component( role = MyComponentExtension.class, hint = "..." ) private MyComponent component; @Parameter( defaultValue = "${session}", readonly = true ) private MavenSession session; @Parameter( defaultValue = "${project}", readonly = true ) private MavenProject project; @Parameter( defaultValue = "${mojoExecution}", readonly = true ) private MojoExecution mojo; @Parameter( defaultValue = "${plugin}", readonly = true ) private PluginDescriptor plugin; @Parameter( defaultValue = "${settings}", readonly = true ) private Settings settings; @Parameter( defaultValue = "${project.basedir}", readonly = true ) private File basedir; @Parameter( defaultValue = "${project.build.directory}", readonly = true ) private File target; public void execute() { } } |
[…] Maven 插件开发 […]