Maven作为一个优秀的项目管理工具,其插件机制为其功能扩展提供了非常大的便利性。Maven本身提供了很多的插件。如果现有的maven插件无法满足我们的需要,可以自己开发一个。
一、命名规范
Maven的官方插件命名格式为maven-xxx
-plugin。为了避免侵犯官方商标,我们一般将自己开发的插件命名为xxx
-maven-plugin。遵守这个规范,可以简化插件的运行命令。
二、创建插件项目
Maven的插件是一个Mojo(Maven plain Old Java Object)工程,每一个Mojo就是Maven中的一个执行目标(executable goal),而插件是对单个或多个相关的Mojo做统一分发。一个Mojo就是一个简单的Java类。
以Idea为例,说明下如何创建Mojo工程。
依次点击New
-> Project
-> Maven
,勾选Create from archetype
,选择maven-archetype-mojo
,填写好相关信息,即可创建一个简单的Mojo工程。
创建好Mojo工程之后,项目工程里会默认生成一个MyMojo类,内容如下:
/**
* Goal which touches a timestamp file.
*
* @goal touch
*
* @phase process-sources
*/
public class MyMojo extends AbstractMojo {
/**
* Location of the file.
* @parameter expression="${project.build.directory}"
* @required
*/
private File outputDirectory;
public void execute() throws MojoExecutionException {
File f = outputDirectory;
if ( !f.exists() ) {
f.mkdirs();
}
File touch = new File( f, "touch.txt" );
FileWriter w = null;
try {
w = new FileWriter( touch );
w.write( "touch.txt" );
} catch ( IOException e ) {
throw new MojoExecutionException( "Error creating file " + touch, e );
} finally {
if ( w != null ) {
try {
w.close();
} catch ( IOException e ) {
// ignore
}
}
}
}
}
Mojo类继承自AbstractMojo,每个Mojo类都有一个execute方法,用来实现插件的逻辑。上面自动生成的代码逻辑很简单,找到项目的输出目录(项目根目录下的target文件夹),在该目录下创建一个名为touch.txt的文件,文件内容写入"touch.txt"。
三、使用插件
3.1. Install
在hello-maven-plugin项目中执行maven的install命令
3.2. 引入自定义插件
新建或打开另一个maven项目(此处使用的是hello-world项目),在pom文件的 build -> plugins 中加入如下内容:
<build>
<plugins>
// ...
<plugin>
<groupId>com.justz</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0.0-SNAPSHOT</version>
</plugin>
</plugins>
</build>
在该项目的Maven Projects
中可以看到hello插件。因为插件项目的artifactId为hello
-maven-plugin,名称中的maven和plugin会被忽略,所以插件名称就是一个简单的hello。
展开插件可以看到Mojo列表,由于项目里只有一个Mojo,且Mojo上用注释@goal
指定了名称为touch
,所以这里只能看到一个hello:touch
。
3.3. 执行插件
双击hello:touch
就会执行这个插件。执行完毕后,可以看到项目根目录的target文件下多了个touch.txt文件。
3.4. 命令行执行插件
命令格式为mvn groupId:artifactId:version:goal
,执行如下命令:
mvn com.justz:hello-maven-plugin:1.0.0-SNAPSHOT:touch
由于我们的插件命名符合规范,所以上面的命令可以简写为:
mvn hello:touch
四、Mojo配置方式
Mojo的配置有两种方式,一种是JavaDoc + Tag
,即上面的示例代码所使用的方式。另一种是注解,使用@Mojo
, @Parameter
等annotation来配置。
上例中各个Tag的作用:
-
@goal
指定了这个mojo的名称 -
@phase
插件生效的生命周期 -
@parameter
用于参数,通过expression为参数注入指定的值,比如例子中的${project.build.directory}
。project.build.directory
是maven内置的属性,代表项目build的根目录。其他内置属性可参考下面。 -
@required
代表参数是必须的
五、开发Mojo
上面的例子使用的是JavaDoc的方式,接下来使用注解的方式来开发一个自定义的Mojo。
5.1 添加依赖
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.3</version>
</dependency>
5.2 添加Mojo
package com.justz;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
@Mojo(name = "greeting", defaultPhase = LifecyclePhase.PROCESS_SOURCES)
public class HelloMojo extends AbstractMojo {
public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info("========= Hello ==========");
}
}
指定Mojo的名称为greeting。逻辑很简单,调用父类的getLog()方法在日志中打印一句话。
5.3 指定plugin版本
在hello-maven-plugin项目的pom文件中指定maven-plugin-plugin的版本。plugin版本过低会导致无法识别到带@Mojo
注解的类,此处使用的版本为3.2。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.2</version>
</plugin>
</plugins>
</build>
5.4 运行
在hello-world
项目中,执行mvn hello:greeting
,可以在输出日志看到要打印的那句话
➜ hello-world mvn hello:greeting
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building hello-world 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- hello-maven-plugin:1.0.0-SNAPSHOT:greeting (default-cli) @ hello-world ---
[INFO] ========= Hello ==========
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.443s
[INFO] Finished at: Wed Nov 14 13:39:47 CST 2018
[INFO] Final Memory: 6M/309M
[INFO] ------------------------------------------------------------------------
六、调试插件
当自定义插件在执行过程中出现了错误时,断点调试一般是最快的解决办法。接下来演示下如何在idea中调试maven插件。
6.1 执行调试命令
在hello-world项目中执行插件调试命令:
mvnDebug hello:greeting
在控制台中可以看到如下输出:
➜ hello-world mvnDebug hello:greeting
Preparing to Execute Maven in Debug Mode
Listening for transport dt_socket at address: 8000
项目正在监听本地的8000端口
6.2 添加远程调试
在hello-maven-plugin项目中,Edit Configurations,在弹出来的对话框中,点击左上角的"+",选择Remote
,填写相关内容。因为是本地调试,Host就是localhost。根据上一步的输出结果,将Port改为8000。Name选填,其他不用动,点击OK。
6.3 执行调试
在execute方法中打好断点,以debug方式启动remote即可。
七、参数的使用
7.1 使用系统内置属性
通过参数实现一个小功能,打印项目根目录下的文件名
@Mojo(name = "greeting", defaultPhase = LifecyclePhase.PROCESS_SOURCES)
public class HelloMojo extends AbstractMojo {
@Parameter(defaultValue = "${project.basedir}")
private File baseDir;
public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info("========= Hello ==========");
File[] files = baseDir.listFiles();
for (File file : files) {
getLog().info(file.getName());
}
}
}
7.2 使用自定义属性
自定义一个greeting.name
属性,输出在hello后面。
@Mojo(name = "greeting", defaultPhase = LifecyclePhase.PROCESS_SOURCES)
public class HelloMojo extends AbstractMojo {
@Parameter(defaultValue = "${project.basedir}")
private File baseDir;
@Parameter(defaultValue = "${greeting.name}")
private String name;
public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info(String.format("========= Hello, %s! ==========", name));
File[] files = baseDir.listFiles();
for (File file : files) {
getLog().info(file.getName());
}
}
}
给name属性赋值有两种方式:
1. 使用-D指定,执行如下命令
mvn hello:greeting -Dgreeting.name=Tom
2. 通过pom文件指定
<plugin>
<groupId>com.justz</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0.0-SNAPSHOT</version>
<executions>
<execution>
<phase>process-sources</phase>
<goals>
<goal>greeting</goal>
</goals>
<configuration>
<name>tom</name>
</configuration>
</execution>
</executions>
</plugin>
八、Maven相关知识点
8.1 生命周期
-
validate
: 验证 -
initialize
: 初始化构建状态,例如设置属性或创建目录 -
generate-sources
: 生成包含在编译中的任何源代码 -
process-sources
: 处理源代码 -
generate-resources
: 生成包含在包中的资源 -
process-resources
: 将资源复制并处理到目标目录中,准备打包 -
compile
: 编译项目的源代码 -
process-classes
: 处理编译后生成的文件 -
generate-test-sources
: 生成包含在编译中的任何测试源代码 -
process-test-sources
: 处理测试源代码 -
generate-test-resources
: 创建测试资源 -
process-test-resources
: 将资源复制并处理到测试目标目录中 -
test-compile
: 将测试源代码编译到测试目标目录中 -
process-test-classes
: 处理编译后产生的测试文件 -
test
: 测试 -
prepare-package
: 预打包 -
package
: 打包 -
verify
: 验证 -
install
: 安装 -
deploy
: 部署
8.2 属性
8.2.1 内置属性
Maven预定义,用户可以直接使用
-
${basedir}
表示项目根目录,即包含pom.xml文件的目录; -
${version}
表示项目版本; -
${project.basedir}
同${basedir}; -
${project.baseUri}
表示项目文件地址; -
${maven.build.timestamp}
表示项目构件开始时间; -
${maven.build.timestamp.format}
表示属性${maven.build.timestamp}的展示格式,默认值为yyyyMMdd-HHmm,可自定义其格式,其类型可参考java.text.SimpleDateFormat。用法如下:
<properties>
<maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss</maven.build.timestamp.format>
</properties>
8.2.2 POM属性
使用pom属性可以引用到pom.xml文件对应元素的值
-
${project.build.directory}
表示主源码路径; -
${project.build.sourceEncoding}
表示主源码的编码格式; -
${project.build.sourceDirectory}
表示主源码路径; -
${project.build.finalName}
表示输出文件名称; -
${project.version}
表示项目版本,与${version}相同;