Maven实战读书笔记(18)

编写Maven插件的一般步骤

1、创建一个maven-plugin项目:插件本身也是Maven项目,特殊的地方在于它的packaging必须是maven-plugin,用户可以使用maven-archetype-plugin快速创建一个Maven插件项目

2、为插件编写目标:每个插件都必须包含一个或者多个目标,Maven称之为Mojo(POJO对应,后缀指Plain Old Java Object,这里指Maven Old Java Object)。编写插件的时候必须提供一个或者多个继承自AbstractMojo的类

3、为目标提供配置点:大部分Maven插件及其目标都是可配置的,因此在编写Mojo的时候需要注意提供可配置的参数

4、编写代码实现目标行为:根据实际的需要实现Mojo

5、错误处理及日志:当Mojo发生异常时,根据情况控制Maven的运行状态。在代码中编写必要的日志以便为用户提供足够的信息

6、测试插件:编写自动化的测试代码测试行为,然后再实际运行插件以验证其行为

 

编写一个用于代码行统计的Maven插件

使用该插件,用户可以了解到Maven项目中各个源代码目录下文件的数量,以及它们加起来共有多少代码行。

不过,强烈方队使用代码行来考核程序员,因为大家都知道,代码的数量并不能真正反映一个程序员的价值

 

代码行统计插件的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.juvenxu.mvnbook</groupId>
  <artifactId>maven-loc-plugin</artifactId>
  <packaging>maven-plugin</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>maven-loc-plugin Maven Mojo</name>
  <url>http://maven.apache.org</url>
  <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>

Maven插件项目的POM有两个特殊的地方:

1、它的packaging必须为maven-plugin,这种特殊的打包类型能控制Maven为其在生命周期阶段绑定插件处理相关的目标,例如在compile阶段,Maven需要为插件项目构建一个特殊插件描述符文件

2、从上述代码中可以看到一个artifactIdmaven-plugin-api的依赖,该依赖中包含了插件开发所必须的类,例如稍后会看到的AbstractMojo,需要注意的是,并没有使用默认Archetype生成的maven-plugin-api版本,而是升级到了3.0,这样做的目的是与Maven的版本保持一致

 

CountMojo的主要代码

package com.juvenxu.mvnbook.loc;
 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
 
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
 
/**
 * Goal which counts lines of code of a project
 *
 * @goal count
 */
public class CountMojo extends AbstractMojo {
      
       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;
      
    /**
     * The file types which will be included for counting
     *
     * @parameter
     */
       private String[] includes;
 
       public void execute() throws MojoExecutionException {
              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 resource : testResources) {
                            countDir(new File(resource.getDirectory()));
                     }
              } catch (IOException e) {
                     throw new MojoExecutionException("Unable to count lines of code.", e);
              }
       }
      
       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");
       }
      
       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);
                     }
              }
       }
      
       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;
       }
 
}

1、每个插件目标类,或者说Mojo,都必须继承AbstractMojo并实现execute()方法,只有这样maven才能识别该插件目标,并执行execute()方法中的行为

2、由于历史原因,上述的CountMojo类使用了Java 1.4风格的标注 (将标注写在注释中),这里要关注的是@goal,任何一个Mojo都必须使用该标注写明自己的目标名称

3、有了目标定义名称之后,我们才能在项目中配置该插件目标,或者在命令行调用之

4mvn com.juvenxu.mvnbook:maven-loc-plugin:0.0.1-SNAPSHOT:count

5、还包含了basedirsourceDirectorytestSourceDirectory等字段,它们都使用了@parameter标注,但同时关键字expression表示从系统属性读取这几个字段的值

6${project.basedir}项目的基础目录,${project.build.sourceDirectory}主代码目录,${project.build.testSourceDirectory}测试代码目录

7@readonly标注表示不允许用户对其进行配置,因为对于一个项目来说,这几个目录位置都是固定的

 

了解这些简单的配置点之后,下一步就该实现插件的具体行为了

1、从execute()方法中可以看到,如果用户没有配置includes则就是用默认的统计包含配置

2、然后再分别统计项目主代码目录、测试代码目录、主资源目录,以及测试资源目录

3、这里涉及了一个countDir()方法

 

简单解释CollectFiles()countLine()CountDir()这三个方法

1collectFiles()方法用来递归地搜集一个目录下所有应当被统计的文件

2countLine()方法用来统计单个文件的行数

3countDir()则借助上述两个方法统计某个目录下共有多少文件被统计,以及这些文件共包含了多少代码航

 

简单解释execute()方法包含的异常处理

1execute()方法包含了简单的异常处理,代码行统计的时候由于涉及了文件操作,因此可能会抛出IOException

2、当捕获到IOException的时候,使用MojoExecutationException对其简单包装后再抛出

3Maven执行插件目标的时候如果遇到MojoExecutationException,就会在命令行显示"BULD ERROR" 信息

 

countDir()方法是最后一行使用了AbstractMojogetLog()方法

1、该方法返回一个类似于Log4j的日志对象,可以用来将输出日志到Maven命令行

2、这里使用了info级别的日志告诉用户某个路径下有多少文件被通缉,共包含了多少代码行

 

下一步是为插件提供配置点

1、我们希望该插件默认统计所有的JavaXML,以及properties文件,但是允许用户配置包含哪些类型的文件

2includes字段就是用来为用户提供该配置点的,它的类型为String数组,并且使用了@parameter参数表示用户可以在使用该插件的时候在POM中配置该字段

 

配置CountMojoincludes参数

<plugin>
       <groupId>com.juvenxu.mvnbook</groupId>
       <artifactId>maven-loc-plugin</artifactId>
       <version>0.0.1-SNAPSHOT</version>
       <configuration>
              <includes>
                     <include>java</include>
                     <include>sql</include>
              </includes>
       </configuration>
</plugin>

配置CountMojo统计JavaSQL文件,而不是默认的JavaXMLproperties

 

使用mvn clean install命令将该插件项目构建并安装到本地仓库

 

如果先命令行太长太复杂,可以将该插件的groupId添加到settings.xml

<settings>
       <pluginGroups>
              <pluginGroup>com.juvenxu.mvnbook</pluginGroup>
       </pluginGroups>
</settings>

现在Maven命令行就可以简化成:mvn loc:count

 

Mojo标注

每个Mojo都必须使用@Goal标注来注明其目标名称,否则Maven将无法识别该目标

 

Mojo的标注不仅限于@Goal,以下是一些可以用来控制Mojo行为的标注

n         @goal <name>,这是唯一必须声明的标注,当用户使用命令行调用插件,或者在POM中配置插件的时候,都需要使用该目标名称

n         @phase <phase>,默认将该目标绑定至Default声明周期的某个阶段,这样在配置使用该插件目标的时候就不需要声明phase,例如,maven-surefire-plugintest目标就带有@phase test标注

n         @requiresDependencyResolution <scope>,表示在运行该Mojo之前必须解析所有指定范围的依赖。例如,maven-surefire-plugintest目标带有@requiresDependencyResolution test标注,表示在执行测试之前,所有测试范围的依赖必须得到解析。这里可用的依赖范围有compiletestruntime,默认值为runtime

n         @requiresProject <true/false>,表示该目标是否必须在一个Maven项目中运行,默认为true。大部分插件目标都需要依赖一个项目才能执行,但有一些例外。例如maven-help-pluginsystem目标,它用来显示系统属性和环境变量信息,不需要实际项目,因此使用了@requiresProject false标注。另外maven-archetype-plugingenerate目标也是一个很好的例子

n         @requiresDirectInvocation <true/false>,当值为true的时候,该目标就只能通过命令行直接调用,如果试图在POM中将其绑定到生命周期阶段,Maven就会报错,默认值为false。如果你希望编写的插件只能在命令行独立运行,就应当使用该标注

n         @requiresOnline <true/false>,表示是否要求Maven必须是在线状态,默认值是false

n         @requiresReport <true/false>,表示会否要求项目报告已经生成,默认值是false

n         @aggregator,当Mojo在多模块项目上运行时,使用该标注表示该目标只会在顶层模块运行,例如maven-javadoc0pluginaggregator-jar使用了@aggregator标注,它不会为多模块项目的每个模块生成javadoc,而是在顶层项目生成一个已经聚合的Javadoc文档

n         @execute goal = "<goal>",在运行该目标之前先让Maven运行另外一个目标,如果是本插件的目标,则直接使用目标名称,否则使用"prefix:goal"的形式,即注明目标前缀。例如,maven-pmd-plugin是一个使用PMD来分析项目源码的工具,它包含pmdcheck等目标,其中pmd用来生成报告,而check用来验证报告。由于check是依赖于pmd生成的内容的,因此可以看到它使用了标注@execute goal = pmd

n         @execute phase = "<phase>",在运行该目标之前让Maven选运行一个并行的生命周期,到指定的阶段为止。例如maven-dependency-pluginanalyze使用了标注@execute phase=test-compile”,因此当用户在命令行执行dependencyanalyze的时候,Maven会首先执行default声明周期所有至test-compile的阶段

n         @execute lifecycle = "<lifecycle>" phase = "<phase>",在运行该目标之前让Maven先运行一个自定义的生命周期,到指定的阶段为止。例如maven-surefire-report-plugin这个用来生成测试报告的插件,它有一个report目标,标注了@execute phase = "test" lifecycle = "surefire",表示运行这个自定义的urefire声明周期至test阶段。自定义生命周期的配置文件位于src/main/resources/META-INF/maven/lifecycle.xml

 

maven-surefire-report-plugin的自定义生命周期

<lifecycles>
       <lifecycle>
              <id>surefire</id>
              <phases>
                     <phase>
                            <id>test</id>
                            <configuration>
                                   <testFailureIgnore>true</testFailureIgnore>
                            </configuration>
                     </phase>
              </phases>
       </lifecycle>
</lifecycles>

 

Mojo参数

1、我们可以使用@parameterMojo的某个字段标注为可配置的参数,即Mojo参数

2、事实上几乎每个Mojo都有一个或者多个Mojo的参数,通过配置这些参数,Maven用户可以自定义插件的行为

3Maven支持种类多样的Mojo参数,包括单值的booleanintfloatStringDateFileURL,多值的数组、CollectionMapProperties


n         boolean (包含booleanBoolean)

/**

* @parameter

*/

private boolean sampleBoolean

对应的配置如下:

<sampleBoolean>true</sampleBoolean>

n         int (包含IntegerlongLongshortShortbyteByte)

/**

* @parameter

*/

private int sampleInt

对应的配置如下

<sampleInt>8</sampleInt>

n         float(包含FloatdoubleDouble

/**

* @parameter

*/

private float sampleFloat

对应的配置如下

<sampleFloat>8.8</sampleFloat>

n         String(包含StringBuffercharCharacter

/**

* @parameter

*/

private String sampleString

对应的配置如下

<sampleString>Hello World</sampleString>

n         Date(格式为yyyy-MM-dd HHmmss.S a或者yyyy-MM-dd HH:mm:ssa

/**

* @parameter

*/

private Date sampleDate

对应的配置如下:

<sampleDate>2010-06-06 3:4:55.1 PM</sampleDate>

或者

<sampleDate>2010-06-06 3:14:55PM</sampleDate>

n         File

/**

* @ parameter

*/

private File sampleFile

对应的配置如下:

<sampleFile>c:/tmp<./sampleFile>

n         URL

/**

* @parameter

*/

private URL sampleURL
对应配置如下:

<sample=URL>http://www.juvenxu.com</sampleURL>

n         数组

/**

* @parameter

*/

private String[] includes

对应的配置如下:

<includes>

       <include>java</include>

       <include>sql</include>

</includes>

n         Collection (任何实现Collection接口的类,如ArrayListHashSet)

/**

* @parameter

*/

private List includes

对应的配置如下:

<includes>

       <include>java</include>

       <include>sql</include>

</includes>

n         Map

/**

* parameter

*/

private Map sampleMap

对应的配置如下:

<sampleMap>

       <key1>value1</key1>

       <key2>value2</key2>

</sampleMap

n         Properties

/**

* @parameter

*/

private Properties sampleProperties

对应的配置如下:

<sampleProperties>

       <property>

              <name>p_name_1</name>

              <value>p_value_1</value>

       </property>

       <property>

              <name>p_name_2</name>

              <value>p_value_2</value>

       </property>

</sampleProperties>

一个简单的@parameter标注就能让用户配置各种类型的Mojo字段,不过在此基础上,用户还能为@parameter标注提供一些额外的属性,进一步自定义Mojo参数

n         @parameter alias = "<aliasName>"

使用alias,用户就可以为Mojo参数使用别名,当Mojo字段名称太长或者可读性不强时,这个别名就非常有用

/**

* @parameter alias = "uid"

*/

private String uniqueIdentity

对应的配置如下:

<uid>juven</uid>

n         @parameter expression = "${aSystemProperty}"

使用系统属性表达式为Mojo参数进行赋值,这是非常有用的特性。配置@parameterexpression之后,用户可以再命令行配置该Mojo参数。例如,maven-surefire-plugintest目标有如下源码:

/**

* @parameter expression="${maven.test.skip}"

*/

private boolean skip

用户可以在POM中配置skip参数,同时也可以直接在命令行使用-Dmaven.test.skip=true来跳过测试,如果Mojo参数没有提供expression,那就意味着该参数无法在命令行直接配置。还需要注意的是,Mojo参数的名称和expression名称不一定相同

n         @parameter defaut-value = "aValue/ ${anExpression}"

如果用户没有配置该Mojo参数,就为其提供一个默认值。该值可以是一个简单字面量,如"true""hello"或者“1.5”,也可以是一个表达式,以方便使用POM的某个元素

例如,下面代码中的参数sampleBoolean默认值为true

/**

* @parameter defaultValue="true"

*/

private boolean sampleBoolean

代码清单

/**

* @parameter expression="${project.build.sourceDirectory}"

* @required

* @readonly

*/

private File sourceDirectory;

表示默认使用POM元素<project><build><sourceDirectory>的值

除了@parameter标注外,还看到可以为Mojo参数使用@readonly@required标注

n         @readonly

表示该Mojo参数是只读的,如果使用了该标注,用户就无法对其进行配置。通常为应用POM元素内容的时候,我们不希望用户干涉

n         @required

表示该Mojo参数是必须的,如果使用了该标注,但是用户没有配置该Mojo参数且其没有默认值,Maven就会报错

 

关于错误处理和日志

1AbstractMojo实现了Mojo接口,execute()方法正是在这个接口中定的

void execute() throws MojoExecutionException, MojoFailureException;

2、这个方法可以抛出两种异常,分别是MojoExecutionExceptionMojoFailureException

3、如果Maven执行插件目标的时候遇到MojoFailureException,就会显示“BUILD FAILURE”的错误信息,这种异常表示Mojo在运行时发现了预期的错误。例如maven-surefire-plugin运行后若发现有失败的测试就会抛出该异常

4、如果Maven执行插件目标的时候遇到MojoExecutionException,就会显示"BUILD ERROR"的错误信息。这种异常表示Mojo在运行时发现了未预期的错误,比如代码统计插件何时会遇到IOException,我们并不知道,这个时候只能将其嵌套进MojoExecutation后再抛出

 

AbstractMojo提供了一个getLog()方法

该方法返回Log对象,该对象支持四种级别的日志方法,它们从低到高分别为:

n         debug,调试级别的日志。Maven默认不会输出该级别的日志,不过用户可以在执行mvn命令的时候使用 -X参数开启调试日志,该级别的日志是用来帮助程序员了解插件具体运行状态的,因此应该尽量详细。需要注意的是,不要指望你的用户会主动去看该界别的日志

n         info,消息级别的日志。Maven默认会输出该级别的日志,该级别的日志应该足够简洁,帮助用户了解插件重要的与运行状态。例如,maven-compiler-plugin会使用该级别的日志告诉用户源代码编译的目标目录

n         warn:警告级别的日志,当插件运行的时候遇到了一些问题或错误,不过这类问题不会导致运行失败的时候,就应该使用该级别的日志警告用户尽快修复

n         error:错误级别的日志。当插件运行的时候遇到了一些问题或者错误,并且这类问题导致Mojo无法继续运行,就应该使用该级别的日志提供详细的错误信息

 

上述每个级别的日志都提供了三个方法

n         void debug (CharSequence content)

n         void debug (CharSequence content, Throwable error)

n         void debug(Throwable error)

在编写插件的时候,应该根据实际情况选择适应的方法。基本的原则是,如果有异常出现,就应该尽量使用适宜的日志方法将异常堆栈记录下来,方便将来的问题分析

 

编写自动化的集成测试代码来验证Maven插件的行为

1、既然是集成测试,那么就一定需要一个实际的Maven项目,配置该项目使用插件,然后在该项目上运行Maven构建,最后再验证该构建成功与否,可能还需要检查构建的输出

2Maven社区有一个用来帮助插件集成测试的插件,它就是maven-invoker-plugin,该插件能够用来在一组项目上执行maven,并检查每个项目的构建是否成功,最后,它还可以执行BeanShell或者Groovy脚本来验证项目构建的输出

 

BeanShellGroovy

BeanShellGrooy都是基于JVM平台的脚本语言,读者可以访问http://www.beanshell.org/http://groovy.codehaus.org了解更多的信息




转载于:https://my.oschina.net/u/814431/blog/360424

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值