【Maven教程】(十五):编写 Maven 插件—— 编写 Maven 插件的一般步骤及案例、Mojo 标注与参数、错误处理和日志 ~

news2024/11/16 10:55:36

Maven · 编写 Maven 插件

  • 1️⃣ 编写 Maven 插件的一般步骤
  • 2️⃣ 案例:编写一个用于代码行统计的 Maven 插件
  • 3️⃣ Mojo 标注
  • 4️⃣ Mojo 参数
  • 5️⃣ 错误处理和日志
  • 6️⃣ 测试 Maven 插件
  • 🌾 总结

前面文章已经讲过,Maven 的任何行为都是由插件完成的,包括项目的清理、编译、 测试以及打包等操作都有其对应的Maven 插件。每个插件拥有一个或者多个目标,用户可 以直接从命令行运行这些插件目标,或者选择将目标绑定到Maven 的生命周期。

大量的 Maven 插件可以从 Aapche 和 Codehaus 获得,这里的近百个插件几乎能够满足所有Maven 项目的需要。除此之外,还有很多 Maven 插件分布在 Googlecode 、Sourceforge、 Github 等项目托管服务中。因此,当你发现自己有特殊需要的时候,首先应该搜索一下看是否已经有现成的插件可供使用。例如,如果想要配置Maven 自动为所有Java 文件的头部添加许可证声明,那么可以通过关键字 maven plugin license 找到 maven-license-plugin, 这个托管在 Googlecode 上的项目完全能够满足我的需求。

在一些非常情况下(几率低于1%), 你有非常特殊的需求,并且无法找到现成的插件 可供使用,那么就只能自己编写 Maven 插件了。编写 Maven 插件并不是特别复杂,本文将 详细介绍如何一步步编写能够满足自己需要的 Maven 插件。

在这里插入图片描述


1️⃣ 编写 Maven 插件的一般步骤

为了能让大家对编写 Maven 插件的方法和过程有一个总体的认识,下面先简要介绍一下编写 Maven 插件的主要步骤。

  • 创建一个 maven-plugin 项目:插件本身也是Maven项目,特殊的地方在于它的 packaging 必须是 maven-plugin, 用户可以使用 maven-archetype-plugin 快速创建一个 Maven 插件项目。
  • 为插件编写目标:每个插件都必须包含一个或者多个目标, Maven 称之为 Mojo ( 与 POJO对应,后者指 Plain Old Java Object, 这里指 Maven Old Java Object)。编写插件的时候 必须提供一个或者多个继承自 AhstractMojo 的类。
  • 为目标提供配置点:大部分Maven 插件及其目标都是可配置的,因此在编写Mojo的 时候需要注意提供可配置的参数。
  • 编写代码实现目标行为:根据实际的需要实现 Mojo。
  • 错误处理及日志:当Mojo发生异常时,根据情况控制Maven 的运行状态。在代码中 编写必要的日志以便为用户提供足够的信息。
  • 测试插件:编写自动化的测试代码测试行为,然后再实际运行插件以验证其行为。

2️⃣ 案例:编写一个用于代码行统计的 Maven 插件

为了便于大家实践,下面将详细演示如何实际编写一个简单的用于代码行统计的 Maven 插件。使用该插件,用户可以了解到 Maven 项目中各个源代码目录下文件的数量,以及它们加起来共有多少代码行。不过,有些朋友强烈反对使用代码行来考核程序员,因为大家都知道,代码的数量并不能真正反映一个程序员的价值。

要创建一个 Maven插件项目,首先使用 maven-archetype-plugin 命令:

$mvn archetype:generate

然后选择:

maven-archetype-plugin (An archetype which contains a sample Maven plugin.)

输入Maven坐标等信息之后, 一个 Maven插件项目就创建好了。打开项目的 pom.xml 可以看到如代码所示的内容。

<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.xiaoshan.mvnbook</groupId>
	<artifactId>maven-loc-plugin</artifactId>
	<packaging>maven-plugin</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>Maven LOC Plugin</name>
	<url>http://www.xiaoshan.com/</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)从上述代码中可以看到一个 artifactld 为 maven-plugin-api的依赖,该依赖中包含了 插件开发所必需的类,例如稍后会看到的 AbstractMojo。需要注意的是,代码中 并没有使用默认 Archetype 生成的 maven-plugin-api 版本,而是升级到了3.0,这样做的目的是与Maven 的版本保持一致。

插件项目创建好之后,下一步是为插件编写目标。使用Archetype 生成的插件项目包含 了一个名为MyMojo的 Java 文件,我们将其删除,然后自己创建一个 CountMojo,如代码所示。

/**
* 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 NojoExecutionException{
		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);
		}
	}
}

首先,每个插件目标类,或者说Mojo, 都必须继承 AbstractMojo 并实现 execute() 方法,只有这样Maven 才能识别该插件目标,并执行 execute() 方法中的行为。其次,由于历史原因,上述CountMojo类使用了Java1.4 风格的标注(将标注写在注释中), 这里要关注的是@goal, 任何一个 Mojo都必须使用该标注写明自己的目标名称,有了目标定义之后, 我们才能在项目中配置该插件目标,或者在命令行调用之。例如:

$mvn com.xiaoshan.mvnbook :maven-loc-plugin:0.0.1-SNAPSHOT:count

创建一个Mojo 所必要的工作就是这三项:继承 AbstractMojo、实现 execute() 提供@goal 标注。
下一步是为插件提供配置点。我们希望该插件默认统计所有Java、XML, 方法、以及 properties 文件 , 但是允许用户配置包含哪些类型的文件。 代码中的 includes 字段就是用来为用户提供该配置点的,它的类型为String 数组,并且使用了@parameter 参数表示用户可以在使用该插件的时候在POM 中配置该字段,如代码:

<plugin>
	<groupId>com.xiaoshan.mvnbook</groupId>
	<artifactId>maven-1oc-plugin</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<configuration>
		<includes>
			<include>java</include>
			<include>sql</include>
		</includes>
	</configuration>
	</executions>
</plugin> 

代码配置了 CountMojo统计Java 和 SQL 文件,而不是默认的 Java 、XML 和Properties。
代码中还包含了 basedir 、sourceDirectory 、testSourceDirectory 等字段,它们都使用了@parameter 标注,但同时关键字 expression 表示从系统属性读取这几个字段的值。
${project.basedir} 、${project.build.sourceDirectory}、${project.build.testSourceDirectory} 等表达式读者应该已经熟悉,它们分别表示了项目的基础目录、主代码目录和测试代码目录 。 @readonly 标注表示不允许用户对其进行配置,因为对于一个项目来说,这几个目录位置都是固定的。

了解这些简单的配置点之后,下一步就该实现插件的具体行为了。从前面代码的 execute() 方法中大家能看到这样一些信息:如果用户没有配置 includes 则就是用默认的统计包含配置,然后再分别统计项目主代码目录、测试代码目录、主资源目录,以及测试资源目录。这里涉及一个 countDir() 方法,其具体实现如代码所示。

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;
} 

这里简单解释一下上述三个方法: collectFiles() 方法用来递归地收集一个目录下所有应当被统计的文件,countLine()方法用来统计单个文件的行数,而 countDir()则借助上述两个方法统计某一目录下共有多少文件被统计,以及这些文件共包含了多少代码行。

代码中的execute() 方法包含了简单的异常处理,代码行统计的时候由于涉及了文件操作,因此可能会抛出 IOException。当捕获到 IOException 的时候,使用 MojoExecutationException 对其简单包装后再抛出,Maven 执行插件目标的时候如果遇到 MojoExecutationException, 就会在命令行显示 “BUILD ERROR” 信息。

代码中的 countDir() 方法的最后一行使用了AbstractMojo 的 getLog() 方法, 该方法返回一个类似于Log4j的日志对象,可以用来将输出日志到Maven 命令行。这里使用了 info 级别的日志告诉用户某个路径下有多少文件被统计,共包含了多少代码行,因此在使用该插件的时候可以看到如下的 Maven 输出:

[INFO]---maven-loc-plugin:0.0.1-SNAPSHOT:count(default)@app ---
[INFO]\src\main\java:13 lines of code in 1 files
[INFO]\src\test\java:38 lines of code in 1 files

使用 mvn clean install 命令将该插件项目构建并安装到本地仓库后,就能使用它统计 Maven 项目的代码行了。如下所示:

$mvn com.xiaoshan.mvnbook:maven-loc-plugin:0.0.1-SNAPSHOT:count
[INFO]Scanning  for projects...
[INFO]
[INFO]--------------------------------------------------------------
[INFO]Building  Account  Captcha  1.0.0-SNAPSHOT
[INFO]--------------------------------------------------------------
[INFO]
[INFO]---maven-loc-plugin:0.0.1-SNAPSHOT:count(default-cli) @account-captcha     	
[INFO]\src\main\java:179 lines of code in 4 files
[INFO]\src\test\java:112 lines of code in 2 files
[INFO]\src\main\resources:11 lines of code in 1 files
[INFO]\src\test\resources:0 lines of code in 0 files
[INFO]--------------------------------------------------------------
[INFO]BUILD SUCCESS
[INFO]--------------------------------------------------------------
[INFO]Total time:0.423s
[INFO]Finished at:Sat Jun 0516:28:35 CST 2023
[INFO]Final  Memory:1M/4M
[INFO]--------------------------------------------------------------

如果嫌命令行太长太复杂,可以将该插件的 groupld 添加到 settings.xml 中。如下所示:

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

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

$mvn loc:count

3️⃣ Mojo 标注

每个Mojo 都必须使用 @Goal 标注来注明其目标名称,否则 Maven 将无法识别该目标。 Mojo 的标注不仅限于@Goal, 以下是一些可以用来控制 Mojo 行为的标注。

  • @goal <name>
    这是唯一必须声明的标注,当用户使用命令行调用插件,或者在 POM 中配置插件的时候,都需要使用该目标名称。
  • @phase <phase>
    默认将该目标绑定至 Default生命周期的某个阶段,这样在配置使用该插件目标的时候 就不需要声明 phase 。 例如 ,maven-surefire-plugin 的 test 目标就带有 @phase test 标注。
  • @requiresDependencyResolution <scope>
    表示在运行该 Mojo 之前必须解析所有指定范围的依赖。例如,maven-surefire-plugin 的 test 目标带有@requiresDependencyResolution test 标注,表示在执行测试之前,所有测试范围 的依赖必须得到解析。这里可用的依赖范围有 compile、test 和 runtime, 默认值为 runtime。
  • @requiresProject <true/false>
    表示该目标是否必须在一个 Maven 项目中运行,默认为true 。大部分插件目标都需要依赖一个项目才能执行,但有一些例外。例如 maven-help-plugin 的 system目标,它用来显示系统属性和环境变量信息,不需要实际项目,因此使用了@requiresProject false 标注。另外, maven-archetype-plugin 的 generate 目标也是一个很好的例子。
  • @requiresDirectInvocation <true/false>
    当值为 true的时候,该目标就只能通过命令行直接调用,如果试图在 POM 中将其绑定到生命周期阶段 ,Maven 就会报错,默认 值 为false 。 如果你希望编写的插件只能在命令行 独立运行,就应当使用该标注。
  • @requiresOnline <true/false>
    表示是否要求 Maven 必须是在线状态,默认值是false。
  • @requiresReport <true/false>
    表示是否要求项目报告已经生成,默认值是 false。
  • @aggregator
    当 Mojo 在多模块项目上运行时,使用该标注表示该目标只会在顶层模块运行。例如 maven-javadoc-plugin 的 aggregator-jar 使用了 @aggregator 标注,它不会为多模块项目的每个模块生成Javadoc, 而是在顶层项目生成一个已经聚合的 Javadoc 文档。
  • @execute goal=“<goal>”
    在运行该目标之前先让Maven 运行另外一个目标,如果是本插件的目标,则直接使用 目标名称,否则使用“prefix;goal”的形式,即注明目标前缀。例如, maven-pmd-plugin 是一个使用 PMD来分析项目源码的工具,它包含 pmd 和 check 等目标,其中 pmd 用来生成 报告,而check 用来验证报告。由于check 是依赖于 pmd 生成的内容的,因此可以看到它使用了标注@ execute goul=“pmd”。
  • @execute phase=“<phase>”
    在运行该目标之前让Maven 先运行一个并行的生命周期,到指定的阶段为止。例如 maven-dependency-plugin 的 analyze 使用了标注@execute phase =“test-compile”,因此当用户在 命令行执行 dependeney:analyze 的时候, Maven 会首先执行 default 生命周期所有至 test- compile 的阶段
  • @execute lifecycle=“<Hifecycle>” phase=“<phase>”
    在运行该目标之前让Maven先运行一个自定义的生命周期,到指定的阶段为止。例如 maven-surefire-report-plugin 这个用来生成测试报告的插件,它有一个report 目标,标注了@execute phase=“test” lifecycle=“surefire”,表示运行这个自定义的 surefire 声明周期至 test 阶段。自定义生命周期的配置文件位于 src/main/resources/META-INF/maven/lifecycle.xml。 内容如代码所示。
<lifecycles>
	<lifecycle>
		<id>surefire</id>
		<phases>
			<phase>
				<id>test</id>
				<configuration>
					<testFailureIgnore>true</testFailureIgnore>
				</configuration>
			</phase>
		</phases>
	</lifecycle>
</lifecycles>

4️⃣ Mojo 参数

正如在代码中所看到的那样,我们可以使用@ parameter 将 Mojo的某个字段 标注为可配置的参数,即 Mojo参数。事实上几乎每个 Mojo 都有一个或者多个 Mojo 参数, 通过配置这些参数,Maven用户可以自定义插件的行为。

Maven 支持种类多样的 Mojo 参数,包括单值的 boolean、int、foat、String、Date、File 和 URL, 多值的数组、Collection、Map、Properties 等。

  • boolean (包括 boolean 和 Boolean)

    /**
    *@parameter
    */
    private boolean sampleBoolean
    

    对应的配置如下:

    <sampleBoolean>true</sampleBoolean>
    
  • int ( 包 括 Integer 、long 、Long 、short 、Short 、byte 、Byte)

    /**
    *@parameter
    */
    private int  sampleInt
    

    对应的配置如下:

    <sampleInt>8</sampleInt>
    
  • float ( 包 括 Float 、double 、Double)

    /**
    *@parameter
    */
    private float sampleFloat
    

    对应的配置如下:

    <gampleFloat>8.8</sampleFloat>
    
  • String ( 包 括 StringBuffer 、char 、Character)

    /**
    *@parameter
    */
    private String sampleString
    

    对应的配置如下:

    <sampleString>Hello World</samplestring>
    
  • Date (格式为 yyyy-MM-dd HH:mm:sa.Sa或者 yyyy-MM-dd HH:mm:ssa)

    /**
    *@parameter
    */
    private Date sampleDate
    

    对应的配置如下:

    <sampleDate>2010-06-063:14:55.1 PM</sampleDate>
    

    或者

    <sampleDate>2010-06-063:14:55PM</sampleDate>
    
  • File

    /**
    *@parameter
    */
    private File sampleFile
    

    对应的配置如下:

    <sampleFile>c:\tmp</sampleFile>
    
  • URL

    /**
    *@parameter
    */
    private URL sampleURL
    

    对应的配置如下:

    <sample=URL>http://www.xiaoshan.com/</sampleURL>
    
  • 数组

    /**
    *@parameter
    */
    private String[] includes
    

    对应的配置如下:

    <includes>
    	<include>java</include>
    	<include>sql</include>
    </includes>
    
  • Collection ( 任何实现 Collection 接口的类 , 如 ArrayList 和 HashSet)

    /** 
    *@parameter
    */
    private   List   includeg
    

    对应的配置如下:

    <includes>
    	<include>java</include>
    	<include>sql</include>
    </includes>
    
  • Map

    /**
    *@parameter
    */
    private Map sampleMap
    

    对应的配置如下:

    <sampleMap>
    	<key1>value1</key2>
    	<key1>value2</key2>
    </sampleMap>
    
  • 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 参数。

  • @parameter alias=“<aliasName>”
    使用alias, 用户就可以为Mojo参数使用别名,当Mojo字段名称太长或者可读性不强 时,这个别名就非常有用。例如:

    /**
    *@parameter alias="uid"
    */
    private String uniqueIdentity
    

    对应的配置如下:

    <uid>xiaoshan</uid>
    
  • @parameter expression=“${aSystemProperty}”
    使用系统属性表达式对 Mojo参数进行赋值,这是非常有用的特性。配置了@parameter 的expression之后,用户可以在命令行配置该Mojo参数。例如,maven-surefire-plugin 的 test 目标有如下源码:

    /**
    *@parameter expression="${maven.test.skip}"
    */
    private boolean skip;
    

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

  • @parameter default-value="aValue/ ${anExpresion} "
    如果用户没有配置该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 标注。

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

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


5️⃣ 错误处理和日志

如果大家看一下 Maven 的源码,会发现 AbstractMojo 实 现 了 Mojo 接 口 ,execute() 方法正是在这个接口中定义的。具体代码如下:

void execute() throws MojoExecutionException,MojoFailureException;

这个方法可以抛出两种异常,分别是MojoExecutionException 和 MojoFailureException。

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

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

上述两种异常能够在 Mojo执行出错的时候提供一定的信息,但这往往是不够的,用户在 编写插件的时候还应该提供足够的日志信息,AbstractMojo 提供了一个 getlog()方法,用户可 以使用该方法获得一个 Log 对象。该对象支持四种级别的日志方法,它们从低到高分别为:

  • debug: 调试级别的日志。Maven 默认不会输出该级别的日志,不过用户可以在执行 mvn 命令的时候使用 -X 参数开启调试日志,该级别的日志是用来帮助程序员了解 插件具体运行状态的,因此应该尽量详细。需要注意的是,不要指望你的用户会主 动去看该级别的日志。
  • info: 消息级别的日志。Maven 默认会输出该级别的日志,该级别的日志应该足够简 洁,帮助用户了解插件重要的运行状态。例如,maven-compiler-plugin 会使用该级别 的日志告诉用户源代码编译的目标目录。
  • warn: 警告级别的日志。当插件运行的时候遇到了一些问题或错误,不过这类问题
    不会导致运行失败的时候,就应该使用该级别的日志警告用户尽快修复。
  • error: 错误级别的日志。当插件运行的时候遇到了 一 些问题或错误,并且这类 问 题 导 致 Mojo 无法继续运行 , 就应该使用该级别的日志提供详细的错误信息。

上述每个级别的日志都提供了三个方法。以 debug 为例,它们分别为:

  • void debug(CharSequence content);
  • void debug(CharSequence content,Throwable error);
  • void debug(Throwable error);

用户在编写插件的时候,应该根据实际情况选择适应的方法。基本的原则是,如 果有异常出现,就应该尽量使用适宜的日志方法将异常堆栈记录下来,方便将来的问 题分析。
如果使用过Log4j 之类的日志框架,就应该不会对Maven 日志支持感到陌生,日志不是 一 个 Maven 插件的核心代码,但是为了方便使用和调试,完整的插件应该具备足够丰富的 日志代码。


6️⃣ 测试 Maven 插件

编写 Maven 插件的最后一步是对其进行测试,单元测试较之于一般的Maven 项目无异, 可以参考本系列前面的文章。手动测试 Maven 插件也是一种做法,读者可以将插件安装到本地仓库后,再找个项目测试该插件。本节要介绍的并非上述两种读者已经十分熟悉的测试方法, 而是如何编写自动化的集成测试代码来验证 Maven 插件的行为。
读者可以想象一下,既然是集成测试,那么就一定需要一个实际的Maven 项目,配置
该项日使用插件,然后在该项目上运行 Maven 构建,最后再验证该构建成功与否,可能还需要检查构建的输出。

既然有数以千计的 Maven 插件,那么很可能已经有很多人遇到过上述的需求,因此 Maven 社区有一个用来帮助插件集成测试的插件,它就是 maven-invoker-plugin 。 该插件能 够用来在一组项目上执行 Maven, 并检查每个项目的构建是否成功,最后,它还可以执行 BeanShell 或者 Groovy 脚本来验证项目构建的输出。

BeanShell 和 Groovy 都是基于JVM 平台的脚本语言,读者可以访问http://www.bean- shell.org/和 http://groovy.codehaus.org/以了解更多的信息。本章下面的内容会用到少许的 Groovy 代码,不过这些代码十分简单,很容易理解。
回顾一下前面的代码行统计插件,可以使用Archetype 创建一个最简单的 Maven 项目, 然后在该项目中配置 maven-loc-plugin。如果一切正常,就应该能够看到如下的 Maven 构建 输出:

[INFO]\src\main\java:13 lines of code in 1 files
[INFO]\src\test\java: 38 lines of code in 1 files

为了验证这一行为,先配置 maven-loc-plugin 的 POM使用 maven-invoker-plugin, 如代码所示。

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-invoker-plugin</artifactId>
	<version>1.5</version>
	<configuration>
		<projectsDirectory>src/it</projectsDirectory>
		<goals>
			<goal>install</goal>
		</goals>
		<postBuildHookScript>validate.groovy</postBuildHookScript>
	</configuration>
	<executions>
		<execution>
			<id>integration-test</id>
			<goals>
				<goal>install</goal>
				<goal>run</goal>
			</goals>
		</execution>
	</executions>
</plugin>

代码中maven-invoker-plugin 有三项配置。首先 projectDirectory 用来配置测试 项目的目录,也就是说在src/it目录下存放要测试的 Maven 项目源码;其次 goals 表示在测试项目上要运行的Maven 目标,这里的配置就表示maven-invoker-plugin 会在 src/it 目录下 的各个Maven项目中运行mvn install命令;最后的 postBuildHookScript 表示在测试完成后要 运行的验证脚本,这里是一个groovy 文件。

从代码中我们还看到,maven-invoker-plugin 的两个目标 install 和 run 被绑定 到了integration-test生命周期阶段。这里的 install 目标用来将当前的插件构建并安装到仓库 中供测试项目使用,run 目标则会执行定义好的 mvn命令并运行验证脚本。

当然仅仅该配置还不够,src/it 目录下必须有一个或者多个供测试的 Maven项目,我们 可以使用 maven-archetype-quickstart 创建一个项目并修改POM 使用mvn-loc-plugin, 如代码所示。该测试项目的其余代码不再赘述。

<project xmlns="http://maven.apache,org/PON/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.xiaoshan</groupId>
	<artifactId>app</artifactId>
	<packaging>jar</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>app</name>
	<url>http://maven.apache.org</url>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactid>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>com.xiaoshan.mvnbook</groupId>
				<artifactId>maven-loc-plugin</artifactId>
				<version>0.0.1-SNAPSHOT</version>
				<executions>
					<execution>
						<goals>
							<goal>count</goal>
						</goals>
						<phase>verify</phase>
					</execution>
				</executiong>
			</plugin>
		</plugins>
	</build>
</project>

上面代码就是一个最简单的 POM, 然后配置 maven-loc-plugin 的 count 目标绑定到了verify 生命周期阶段。
测试项目准备好了,现在要准备的是与该项目对应的验证脚本文件,即 validate.groovy,它应该位于 src/it/app 目录下(即上述测试项目的根目录), 内容如下代码所示。

def file =new File(basedir,'build.log')

def countNain = false
def countTest =false

file.eachLine {
	if(it =~ /src.main.java:13 lines of code in 1 files/)
			countMain =true
	if(it =~ /src.test.java:38 lines of code in 1 files/)
			countTest =true
}

if(!countMain)
	throw new RuntimeException("incorrect src/main/java count info");
if(!countTest) 
	throw new RuntimeException("Incorrect src/test/java count info");

这段Groovy 代码做的事情很简单。它首先读取 app 项目目录下的 build.log 文件,当maven-invoker-plugin 构建测试项目的时候,会把mvn 输出保存到项目下 的 build.log 文件中。因此,可以解析该日志文件来验证 maven-loc-plugin 是否输出了正确的代码行信息。

上述 Groovy 代码首先假设没有找到正确的主代码统计信息和测试代码统计信息,然后 它逐行遍历日志文件,紧接着使用正则表达式检查寻找要检查的内容(两个斜杠//中间的内容是正则表达式,而=~表示寻找该正则表达式匹配的内容),如果找到期望的输出, 就 将 countMain 和 countTest 置为true 。 最后,如果这两个变量的值有 false, 就抛出对应的异常信息。

Maven 会首先在测试项目 app 上运行 mvn install 命令,如果运行成功,则再执行 validate.groovy 脚本。只有脚本运行通过且没有异常,集成测试才算成功。
现在在 maven-loc-plugin 下运行 mvn clean install, 就能看到如下的输出:

[INFO]---maven-invoker-plugin:1.5:install (integration-test)@maven-loc-plugin---
[INFO]Installing D:\ws-maven-book\maven-loc-plugin\pom.xm1 to D:\java \repository\com\xiaoshan\mvnbook\maven-loc-plugin\0.0.1-SNAPSHOT\maven-loc-plugin-0.0.1-SNAPSHOT.pom
[INFO]Installing D:\ws-maven-book\maven-loc-plugin\target\maven-loc-plugin- 0.0.1-SNAPSHOT.jar to D:\java\repository\com\xiaoshan\mvnbook\maven-1oc-plugin\0.0.1-SNAPSHOT\maven-loc-plugin-0.0.1-SNAPSHOT.jar
[INFO]
[INFO]---maven-invoker-plugin:1.5:run(integration-test)@maven-loc-plugin ---
[WARNING] Filtering of parent/child POMs is not supported without cloning the projects
[INFO] Building:app\pom.xml
[INFO]..SUCCESS (3.4 s)
[INFO]-------------------------------------------------
[INFO]Build Summary:
[INFO]Passed:1, Failed:0, Errora:0, Skipped:0
[INFO]-------------------------------------------------

从输出中可以看到 maven-invoker-plugin 的 install 目标将当前项目maven-loc-plugin 安装至本地仓库,然后它的 run 目标构建测试项目app, 并最后报告运行结果。
至此,所有 Maven 插件集成测试的步骤就都完成了。

上述样例只涉及了 maven-invoker-plugin 的很少一部分配置点,用户还可以配置:

  • debug (boolean): 是否在构建测试项目的时候开启 debug 输出。
  • settingsFile(File): 执行集成测试所使用的 settings.xml, 默认为本机环境settings.xml。
  • localRepositoryPath(File): 执行集成测试所使用的本地仓库,默认就是本机环境仓库。
  • preBuildHookScript (String): 构建测试项目之前运行的 BeanShell或 Groovy 脚本。
  • postBuildHookScript(String): 构建测试项目之后运行的 BeanShell 或 Groovy 脚本。

要了解更多的配置点,或者查看更多的样例。读者可以访问 maven-invoker-plugin 的站点:http://maven.apache.org/plugins/maven-invoker-plugin/

🌾 总结

Maven 社区提供了成百上千的插件供用户使用,这些插件能够满足绝大部分用户的需求。然而,在极少数的情况下,用户还是需要编写 Maven 插件来满足自己非常特殊的需求。 编写 Maven 插件的一般步骤包括创建一个插件项目、编写Mojo 、为 Mojo提供配置点、实现 Mojo 行为、处理错误、记录日志和测试插件等。本章实现了一个简单的代码行统计插件, 并逐步展示了上述步骤。用户在编写自己插件的时候,还可以参考本章描述的各种 Mojo标注 、Mojo 参数、异常类型和日志接口。最后介绍了如何使用maven-invoker-plugin 实现插件的自动化集成测试。



温习回顾上一篇(点击跳转)
《【Maven教程】(十四):生成项目站点—— 丰富项目信息、项目报告插件、自定义站点外观、国际化及部署站点 ~》

继续阅读下一篇(点击跳转)
《》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1411377.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[设计模式Java实现附plantuml源码~创建型] 复杂对象的组装与创建——建造者模式

前言&#xff1a; 为什么之前写过Golang 版的设计模式&#xff0c;还在重新写Java 版&#xff1f; 答&#xff1a;因为对于我而言&#xff0c;当然也希望对正在学习的大伙有帮助。Java作为一门纯面向对象的语言&#xff0c;更适合用于学习设计模式。 为什么类图要附上uml 因为很…

如何使用Flutter构建高质量的用户界面

Flutter 是一种比较流行的移动应用开发框架&#xff0c;可以让开发者使用一个代码库构建高质量的 iOS 和 Android 应用。Flutter 以其快速、美观、高度可定制等优点吸引了开发社区的广泛关注。但如何使用 Flutter 构建高质量的用户界面呢&#xff1f;下面分为以下几个部分简单的…

聚醚醚酮(Polyether Ether Ketone)PEEK主要作用是什么?

聚醚醚酮&#xff08;Polyether Ether Ketone&#xff0c;PEEK&#xff09;在工程和高性能应用中具有广泛的应用&#xff0c;主要作用包括&#xff1a; 1.结构材料&#xff1a; PEEK因其优异的机械性能&#xff0c;包括高强度、高硬度和耐磨性&#xff0c;常被用作结构件的制造…

企业能源消耗监测管理系统是否可以做好能源计量与能耗分析?

能源消耗与分析是能源科学管理的基础&#xff0c;也可促进能源管理工作的改善&#xff0c;在企业中能源管理系统的作用也愈加重要。 首先&#xff0c;能源计量是能源管理的基础&#xff0c;通过能源精准计老化&#xff0c;容易出现测量设备不准确以及其他一些人为因素原因导致…

蓝凌OA sysUiExtend.do 任意文件上传漏洞复现

0x01 产品简介 蓝凌核心产品EKP平台定位为新一代数字化生态OA平台,数字化向纵深发展,正加速构建产业互联网,对企业协作能力提出更高要求,蓝凌新一代生态型OA平台能够支撑办公数字化、管理智能化、应用平台化、组织生态化,赋能大中型组织更高效的内外协作与管理,支撑商业…

编译PCL Qt程序

使用PCL的qt程序时&#xff0c;提示不是用QVTK编译的&#xff0c;所以需要在编译VTK时打开Qt的编译选项&#xff08;由于CMakeList比较复杂&#xff0c;使用CMakeGui进行配置&#xff0c;PCL同理&#xff09;&#xff0c;编译VTK完成后&#xff0c;编译PCL也需要配置Qt支持&…

【VTKExamples::PolyData】第十九期 ImplicitDataSetClipping

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 前言 本文分享VTK样例ImplicitDataSetClipping,并解析接口vtkIdFilter & vtkBox & vtkClipPolyData,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就…

Pandas ------ 向 Excel 文件中写入含有合并表头的数据

Pandas ------ 向 Excel 文件中写入含有合并表头的数据 推荐阅读引言正文 推荐阅读 Pandas ------ 向 Excel 文件中写入含有 multi-index 和 Multi-column 表头的数据 引言 这里给大家介绍一下如何向 Excel 中写入带有合并表头的数据。 正文 import pandas as pddf1 pd.D…

flutter设置windows是否显示标题栏和状态栏和全屏显示

想要让桌面软件实现全屏和不显示状态栏或者自定义状态栏&#xff0c;就可以使用window_manager这个依赖库&#xff0c;使用起来还是非常方便的&#xff0c;可以自定义显示窗口大小和位置&#xff0c;还有设置标题栏是否展示等内容&#xff0c;也可以设置可拖动区域。官方仓库地…

C语言实现希尔排序算法(附带源代码)

希尔排序 希尔排序&#xff0c;也称递减增量排序算法&#xff0c;是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。 希尔排序是基于插入排序的以下两点性质而提出改进方法的&#xff1a; 插入排序在对几乎已经排好序的数据操作时&#xff0c;效率高&#xff0…

Unity配置表xlsx/xls打包后读取错误问题

前言 代码如下&#xff1a; //文本解析private void ParseText(){//打开文本 读FileStream stream File.Open(Application.streamingAssetsPath excelname, FileMode.Open, FileAccess.Read, FileShare.Read);//读取文件流IExcelDataReader excelRead ExcelReaderFactory…

2011-2022年北大数字普惠金融指数“第五期”(包括省市县)

2011-2022年北大数字普惠金融指数“第五期”&#xff08;包括省市县&#xff09; 1、时间&#xff1a;2011-2022年 其中县级的时间为2014-2022年 2、来源&#xff1a;北大数字普惠金融指数 3、范围&#xff1a;全国31省&#xff0c;337个地级市以及2800个县 4、指标&#x…

利用nginx宝塔免费防火墙实现禁止国外IP访问网站

本章教程&#xff0c;主要介绍&#xff0c;如何利用nginx宝塔面板中的插件免费防火墙&#xff0c;实现一键禁止国外IP访问网站。 目录 一、安装宝塔插件 二、 开启防火墙 一、安装宝塔插件 在宝塔面板中的软件商店&#xff0c;搜索防火墙关键词&#xff0c;找到Nginx免费防火…

Windows 上面双网卡网络,配置为优先IPV4

多数网络游戏加速器是不支持IPV6的&#xff0c;即便支持IPV6也不好用&#xff0c;原因是IPV6在大陆并不是普及的状态&#xff0c;很多资源是没有的。 所以本文会教大家怎么让双IP栈的用户&#xff0c;怎么配置优先适用IPV4&#xff0c;并且IPV6也还可以用。 跟着我的步骤来&am…

Docker容器基本管理

目录 一、概述 &#xff08;一&#xff09;为什么要用到容器 &#xff08;二&#xff09;docker概念 1.镜像 2.容器 3.仓库 &#xff08;三&#xff09;Docker与虚拟机的区别 &#xff08;四&#xff09;Linux namespace的六大类型 二、安装docker容器引擎 &#xff…

代码+视频,R语言forestploter包优雅的绘制孟德尔随机化研究森林图

在既往文章中&#xff0c;我们对孟德尔随机化研究做了一个简单的介绍。我们可以发现&#xff0c;使用TwoSampleMR包做出来的森林图并不是很美观。今天我们使用R语言forestploter包优雅的绘制孟德尔随机化研究森林图。 使用TwoSampleMR包做出来的森林图是这样的 而很多SCI文章…

在linux、window环境搭建kafka环境

一、搭建环境前置准备 下载kafka的官网 http://kafka.apache.org/downloads根据自己的需求选择版本,安装包不区分linux和windows环境,这一个安装包均可部署。 源代码包含kafka的代码文件,使用scala编写的。 二、linux环境 1. 上传安装包 我下载的版本是kafka_2.12-3.6.1…

JOSEF约瑟 过电流继电器 JL15-1200/11 一开一闭 吊车起重机交直流可用

系列型号 JL15-/11: JL15-1.5/11电流继电器JL15-2.5/11电流继电器 JL15-5/11电流继电器JL15-10/11电流继电器 JL15-15/11电流继电器JL15-20/11电流继电器 JL15-30/11电流继电器JL15-40/11电流继电器 JL15-60/11电流继电器JL15-80/11电流继电器 JL15-100/11电流继电器JL1…

论文笔记(四十二)Diff-DOPE: Differentiable Deep Object Pose Estimation

Diff-DOPE: Differentiable Deep Object Pose Estimation 文章概括摘要I. 介绍II. 相关工作III. DIFF-DOPEIV. 实验结果A. 实施细节和性能B. 准确性C. 机器人-摄像机校准 V. 结论VI. 致谢 文章概括 作者&#xff1a;Jonathan Tremblay, Bowen Wen, Valts Blukis, Balakumar Su…

STM32标准库——(3)LED闪烁、LED流水灯、蜂鸣器

1.相关API 1.1 GPIOSpeed_TypeDef /** * brief Output Maximum frequency selection 最大频率选择*/typedef enum { GPIO_Speed_10MHz 1,GPIO_Speed_2MHz, GPIO_Speed_50MHz }GPIOSpeed_TypeDef;1.2 GPIOMode_TypeDef /** * brief Configuration Mode enumeration 配置…