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

<build>
    <plugins>
        // ...
        <plugin>
            <groupId>com.justz</groupId>
            <artifactId>hello-maven-plugin</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </plugin>
    </plugins>
</build>

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

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 添加依赖

<dependency>
    <groupId>org.apache.maven.plugin-tools</groupId>
    <artifactId>maven-plugin-annotations</artifactId>
    <version>3.3</version>
</dependency>

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。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-plugin-plugin</artifactId>
            <version>3.2</version>
        </plugin>
    </plugins>
</build>

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-plugin-remote.png

6.3 执行调试

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

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

<plugin>
    <groupId>com.justz</groupId>
    <artifactId>hello-maven-plugin</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <executions>
        <execution>
            <phase>process-sources</phase>
            <goals>
                <goal>greeting</goal>
            </goals>
            <configuration>
                <name>tom</name>
            </configuration>
        </execution>
    </executions>
</plugin>

八、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。用法如下:
<properties>
  <maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss</maven.build.timestamp.format>
</properties>

8.2.2 POM属性

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

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

参考链接:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容