编写maven插件的步骤
- 创建一个maven-plugin项目:插件本身也是maven项目,只是它的packaging是maven-plugin。
- 为插件编写目标:每个插件必须包含一个或多个目标,maven称之为Mojo。编写插件时必须提供一个或多个继承自AbstractMojo的类。
- 为目标提供配置点:大部分maven插件以及其目标都是可配置的,因此在编写Mojo的时候需要注意提供可配置的参数。
- 编写代码,实现目标。
- 错误处理以及日志,为客户提供足够的信息。
- 测试插件
一:创建maven-plugin项目
创建一个普通的maven项目,只是packaging改为maven-plugin,同时引入依赖maven-plugin-api。pom文件如下:
<?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>com.sawyer.edu</groupId> <artifactId>maven-loc-plugin</artifactId> <version>1.0-SNAPSHOT</version> <packaging>maven-plugin</packaging> <properties> <maven.version>3.0</maven.version> </properties> <dependencies> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-plugin-api</artifactId> <version>${maven.version}</version> </dependency> </dependencies> </project>
二:创建一个CountMojo类。该类所必要的三项工作:继承AbstractMojo、实现execute()方法、提供@goal标注。代码如下:
package com.sawyer.edu.tlsys.plugin; import org.apache.maven.model.Resource; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * maven 代码统计插件 * @author sawyer * @goal count */ public class CountMojo extends AbstractMojo { /** * default includes */ private static final String[] INCLUDES_DEFAULT = {"java", "xml", "properties"}; /** * @parameter expression = "${project.basedir}" * @required * @readonly */ private File basedir; /** * @parameter expression = "${project.build.sourceDirectory}" * @required * @readonly */ private File sourceDirectory; /** * @parameter expression = "${project.build.testSourceDirectory}" * @required * @readonly */ private File testSourceDirectory; /** * @parameter expression = "${project.build.resources}" * @required * @readonly */ private List<Resource> resources; /** * @parameter expression = "${project.build.testResources}" * @required * @readonly */ private List<Resource> testResources; /** * file types which will be included for counting * @parameter */ private String[] includes; /** * execute * @throws MojoExecutionException MojoExecutionException * @throws MojoFailureException MojoFailureException */ public void execute() throws MojoExecutionException, MojoFailureException { if(includes == null || includes.length == 0){ includes = INCLUDES_DEFAULT; } try{ countDir(sourceDirectory); countDir(testSourceDirectory); for(Resource resource : resources){ countDir(new File(resource.getDirectory())); } for(Resource testResource : testResources){ countDir(new File(testResource.getDirectory())); } }catch (Exception e){ throw new MojoExecutionException("count failed:", e); } } /** * 统计某个目录下文件的代码行 * @param dir 目录 * @throws IOException 文件异常 */ private void countDir (File dir) throws IOException{ if(!dir.exists()){ return; } List<File> collected = new ArrayList<File>(); collectFiles(collected, dir); int lines = 0; for(File sourceFile : collected){ lines += countLine(sourceFile); } String path = dir.getAbsolutePath().substring(basedir.getAbsolutePath().length()); getLog().info(path + ": " + lines + " lines of code in " + collected.size() + "files"); } /** * 递归获取文件列表 * @param collected 文件列表list * @param file 文件 */ private void collectFiles(List<File> collected, File file){ if(file.isFile()){ for(String include : includes){ if(file.getName().endsWith("." + include)){ collected.add(file); break; } } }else{ for(File sub : file.listFiles()){ collectFiles(collected, sub); } } } /** * 读取文件的行数 * @param file 文件对象 * @return line * @throws IOException 文件操作异常 */ private int countLine(File file) throws IOException{ BufferedReader reader = new BufferedReader(new FileReader(file)); int line = 0; try{ while(reader.ready()){ reader.readLine(); line++; } }finally { reader.close(); } return line; } }
CountMojo代码
这里要关注的是@goal标注,这个标注就是这个类的目标,定义了目标之后,我们才可以在项目中配置该插件目标。
代码中还包含了basedir、sourceDirectory、testSourceDirectory等字段,它们都使用了@parameter标注,同时关键字expression表示从系统属性中读取这几个字段值。
其次,代码中的includes字段就是用来为用户提供该配置点的,它的类型为String数组,并且使用了@parameter参数表示用户可以自己在pom中配置该字段,使用该插件时的pom配置如下:
<plugin> <groupId>com.iflytek.edu</groupId> <artifactId>maven-loc-plugin</artifactId> <version>1.0-SNAPSHOT</version> <configuration> <includes> <include>java</include> <include>sql</include> </includes> </configuration> <executions> <execution> <phase>compile</phase> <goals> <goal>count</goal> </goals> </execution> </executions> </plugin>
根据上面的pom配置,可以看到incuudes字段标识需要统计的文件后缀,phase表示该插件在compile阶段工作,使用该插件的时候可以看到如下输出信息:
[INFO] --- maven-loc-plugin:1.0-SNAPSHOT:count (default) @ ZX-jobmonitor-webapp --- [INFO] \src\main\java: 778 lines of code in 6files [INFO] \src\test\java: 0 lines of code in 0files [INFO] \src\main\resources: 0 lines of code in 0files
使用mvn clean install命令将该插件项目构建并安装到本地仓库后,并按照上面的pom配置,就可以使用它统计项目的代码了。
三:错误处理和日志
上面的CountMojo类继承自AbstratctMojo,跟踪会发现该抽象类实现了类Mojo接口,execute()方法就是在这个接口中定义的。
void execute() throws MojoExecutionException, MojoFailureException;
这个方法可以抛出两种异常,
如果是MojoFailureException异常,则表示为发现了预期的错误,例如单元测试插件在发现测试失败时就会抛出该异常。
如果是MojoExcutionException异常,则表示发现了未预期的异常,例如上述代码中的IOException等。
四:测试
可在项目中直接集成该插件,也可以在项目目录下用命令行来测试该插件,命令如下:
mvn com.sawyer.edu:maven-loc-plugin:1.0-SNAPSHOT:count