Maven插件开发

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 中加入如下内容:


    
        // ...
        
            com.justz
            hello-maven-plugin
            1.0.0-SNAPSHOT
        
    

在该项目的Maven Projects中可以看到hello插件。因为插件项目的artifactId为hello-maven-plugin,名称中的maven和plugin会被忽略,所以插件名称就是一个简单的hello。

Maven插件开发_第1张图片
hello-world-plugin.png

展开插件可以看到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 添加依赖


    org.apache.maven.plugin-tools
    maven-plugin-annotations
    3.3

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。


    
        
            org.apache.maven.plugins
            maven-plugin-plugin
            3.2
        
    

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。

Maven插件开发_第2张图片
maven-plugin-remote.png

6.3 执行调试

在execute方法中打好断点,以debug方式启动remote即可。

Maven插件开发_第3张图片
maven-plugin-debug.png

七、参数的使用

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文件指定


    com.justz
    hello-maven-plugin
    1.0.0-SNAPSHOT
    
        
            process-sources
            
                greeting
            
            
                tom
            
        
    

八、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。用法如下:

  yyyy-MM-dd HH:mm:ss

8.2.2 POM属性

使用pom属性可以引用到pom.xml文件对应元素的值

  • ${project.build.directory} 表示主源码路径;
  • ${project.build.sourceEncoding} 表示主源码的编码格式;
  • ${project.build.sourceDirectory} 表示主源码路径;
  • ${project.build.finalName} 表示输出文件名称;
  • ${project.version} 表示项目版本,与${version}相同;

参考链接:

  • Maven插件开发指南
  • Mojo Javadoc Tags
  • Annotations

你可能感兴趣的:(Maven插件开发)