前言
Maven本质上是一个插件执行框架。所有工作都由插件完成,在不同的阶段执行不同的插件去完成任务。
- 构建插件将在构建期间执行,并且应
<build/>
在POM 中的元素中对其进行配置。 - 报告插件将在网站生成期间执行,并且应
<reporting/>
在POM 的元素中进行配置。
插件的作用在于,与maven的生命周期的阶段绑定在一起。maven的不同的阶段,可以对应到一个插件中的目标。一个插件中可以有多个目标
Maven中最简单的插件之一是Clean Plugin。其是负责清除Maven项目的目标目录。当您运行“ mvn clean”时,Maven将执行Clean插件中定义的“ clean”目标,并删除目标目录。Clean插件包含可用于自定义插件行为的参数,他决定了目标文件的位置,该参数称为outputDirectory,默认为$ {project.build.directory}。
什么是 Mojo?
Mojo 就是 Maven plain Old Java Object。每一个 Mojo 就是 Maven 中的一个执行目标(executable goal),一个插件工程中可以包含多个Mojo
自定义插件
步骤
1.创建一个插件的maven工程
这里,我们使用 Idea 作为开发工具进行讲解,创建工程选择 Maven,然后在模板中找到 maven-archetype-mojo,点击下一步,输入对应的参数,如:com.qchery/ekjar-maven-plugin/1.0-SNAPSHOT,最后点击完成即可创建一个简单的 Mojo 工程。
idea的这种功能其实是maven提供的一种叫原型的功能,他是根据项目的模板创建出一个建议的工程,其原理也是插件。
例如,我们也可以不用idea来创建工程,我们可以在命令行中手动的创建工程
mvn archetype:generate
通常,原型来自远程存储库,我们通常在settings.xml中设置原型库的位置,这样在执行上面命令的时候才能给你显示出原型列表进行选择。
执行mvn archetype:generate时,其首先要求从内部目录中选择原型,只需输入原型编号,然后就会创建出对应的工程了。
下面根据原型创建出来的插件工程的pom文件
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cp.web</groupId>
<artifactId>custom-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 插件的打包方式 -->
<packaging>maven-plugin</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.version>3.0</maven.version>
</properties>
<dependencies>
<!-- 写maven插件必备的一些api -->
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>${maven.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
2.编写具体的插件代码
// name用来声明该插件的执行目标 ,phase用来指定所属阶段
// defaultPhase 是在插件使用者没有指定该插件的Phase的时候使用的
@Mojo(name = "count", defaultPhase = LifecyclePhase.COMPILE)
public class MyMojo
extends AbstractMojo {
/**
* Maven内部提供的参数,通过${}来进行获取
*
* @parameter expression="${project.build.directory}"
* @required
*/
@Parameter(property = "basedir" ,defaultValue = "${project.build.directory}")
private File outputDirectory;
/**
* @parameter expression = "${project.basedir}"
* @readonly
* @required
*/
@Parameter(property = "baseDir" ,defaultValue = "${project.basedir}")
private File baseDir;
/**
* @parameter expression = "${project.build.sourceDirectory}"
* @readonly
* @required
*/
@Parameter(property = "sourceDirectory" ,defaultValue = "${project.build.sourceDirectory}")
private File sourceDirectory;
/**
* @parameter expression = "${project.build.testSourceDirectory}"
* @readonly
* @required
*/
@Parameter(property = "testSourceDirectory" ,defaultValue = "${project.build.testSourceDirectory}")
private File testSourceDirectory;
/**
* @parameter expression = "${project.build.resources}"
* @readonly
* @required
*
* maven所标识的资源,也就是我们工程中对应maven目录规划下的文件
*/
@Parameter(defaultValue = "${project.build.resources}", property = "resources")
private List<Resource> resources;
/**
* @parameter expression = "${project.build.testResources}"
* @readonly
* @required
*/
@Parameter(property = "testResources",defaultValue = "${project.build.testResources}")
private List<Resource> testResources;
/**
* 也可以获取自定义的属性值,在插件使用者的配置文件中进行传入
* 我们这里定义的这个属性值的含义是包含的文件后缀
* @parameter
*/
@Parameter(property = "includes")
private String[] includes;
private final static String[] INCLUDES_DEFAULT = {"java"};
public void execute() throws MojoExecutionException {
// 如果没有在pom.xml中说明includes,就是用默认的includes。
if(includes == null || includes.length == 0){
includes = INCLUDES_DEFAULT;
}
int totalCount = 0;
try {
//分别统计四种目录下的代码行数。
totalCount += countDir(sourceDirectory);
totalCount += countDir(testSourceDirectory);
for(Resource resource:resources){
totalCount += countDir(new File(resource.getDirectory()));
}
for(Resource resource:testResources){
totalCount += countDir(new File(resource.getDirectory()));
}
} catch (Exception e) {
throw new MojoExecutionException("Unable to count lines of code.",e);
}
getLog().info("total line count:" + totalCount);
}
private int countDir(File file) throws IOException {
if(file == null){
return 0;
}
int res = 0;
if(file.isFile()){
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
while (reader.readLine()!=null){
res++;
}
return res;
}
File[] files = file.listFiles();
for(File f : files){
res += countDir(f);
}
return res;
}
}
3.install到仓库中
我们编写好了插件代码之后,就可以将当前工程install到我们本地仓库中了,随后就可以使用了。
4.使用自定义插件
方式1:命令行执行
mvn groupId:artifactId:version:goal
因为我们已经将插件安装到本地仓库了,我们只需要输入正确的插件坐标,插件目标
maven会自动的到仓库中查找对应的插件执行,但是这种指定是没有传入任何属性值的
就只能获取到maven内部的属性值了
注意:如果不输入版本信息,maven自动使用最新版
方式2:其他maven工程中引入插件
<build>
<plugins>
<plugin>
<groupId>com.cp.web</groupId>
<artifactId>custom-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<configuration>
<!-- 给插件传递参数,这是自定义属性 -->
<includes>
<include>java</include>
</includes>
</configuration>
<executions>
<!-- 配置插件的执行器,因为一个插件工程中可能存在多个可执行的目标 -->
<execution>
<!-- 配置当前目标的id -->
<id>maven-plugin</id>
<!-- 配置生命周期阶段,会覆盖默认的生命周期 -->
<phase>compile</phase>
<goals>
<!--
插件的目标,也就是指定的插件的name
也就是指定执行插件的哪一个类
因为我们一个插件工程中可能存在多个插件,也就是多个目标
-->
<goal>count</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
注意,这种方式对我们插件的artifactId是有要求的,maven定义了一个规范化的名称
我们的格式必须要是下面这两种格式之一,建议使用第一种方式,第二种是maven官方使用的。
${custom-prefix}-maven-plugin
maven-${custom-prefix}-plugin
5.简化执行命令
假设我们现在存在一个插件,情况如下
<groupId>com.cp.web</groupId>
<artifactId>custom-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
插件内部存在一个目标,名称为count
我们可以在setting.xml文件中配置以下内容
<pluginGroups>
<pluginGroup>com.cp.web</pluginGroup>
</pluginGroups>
也就是我们的groupId。
随后我们就可以使用以下命令来执行插件
mvn custom:count
custom就是${prefix}-maven-plugin中的prefix
所以我们插件的artifactId一定要满足这种格式
count:即该插件中的目标
官方手册:https://maven.apache.org/plugin-developers/index.html