Maven3.x 插件开发入门
Maven工具有很多插件,各种各样的插件,让我们开发调试过程中非常方便,但是终究是有你想要的但是现目前插件不能满足的(可能性非常非常低),这个时候就需要使用其他的替代工具,或者是自己来开发一个Maven插件。
事实上,学Maven插件开发对于我们这种小鸟意义并不大,有两个原因:1、我们平常需要使用的,几乎Maven都有提供;2、如果实在没有,那一般来说都有类似的工具作为替代,我们可以去使用另外的工具。但是,我们对Maven使用,研究深入了以后,学学他的插件开发来玩一下还是不错的。这对Maven的精髓思想的理解还是有很大帮助的。
这里的的插件开发工具是Eclipse-Mars版本。
1、创建插件项目:(项目原型选择插件类型)
2、Artifact Id 以xxx-maven-plugin命名,如:gr-maven-plugin,下面Version的内容无所谓。
3、创建完毕之后的结构是这样:
4、修改pom.xml文件,加上2个依赖:分别是maven-plugin-api和maven-plugin-annotations,前者是插件开发API,后者是插件中使用的注解定以的包,注意打包方式为:<packaging>maven-plugin</packaging>。完整的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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mook.plugin</groupId> <artifactId>gr-maven-plugin</artifactId> <version>1.0-RELEASE</version> <packaging>maven-plugin</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-plugin-api</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>org.apache.maven.plugin-tools</groupId> <artifactId>maven-plugin-annotations</artifactId> <version>3.2</version> <scope>provided</scope> </dependency> </dependencies> </project>
5、删掉默认的包,自己新建一个包com.mook.plugin.gr,在这个包下面创建一个类叫做Car,继承AbstractMojo类。重写里面的execute方法。如下:
@Mojo(name = "drive") public class Car extends AbstractMojo { @Override public void execute() throws MojoExecutionException, MojoFailureException { System.out.println("Car drive..."); } }
6、这样插件就开发完成了。我们将插件install到本地仓库。然后在项目组引入,可以是在本插件项目中引入,也可以在其他项目中引入。
<build> <plugins> <plugin> <groupId>com.mook.plugin</groupId> <artifactId>gr-maven-plugin</artifactId> <version>1.0-RELEASE</version> </plugin> </plugins> </build>
7、使用eclipse的Maven插件来运行,这里的Maven插件实质Eclipse的Maven插件,而不是Maven自己的插件。如下:
8、输出结果:Car drive...结果显然是正确的。
9、这里来解释下里面有几个细节。
1、插件artifactId为什么使用xxx-maven-plugin(或者xxx-plugin-maven),这个是约定的,如果这样命名,在启动插件的时候就可以像上面那样gr:drive。否则就的把gr改成groupId:artifactId:version:xxx这种方式,很不方便,当然也有另外的方式处理,在settings文件里面增加pluginManageMent内容,也能实现简化写法。(这个地方改一下,在我们的pom.xml文件中,plugins下面的maven-plugin-plugin中的<goalPrefix>标签下是可以配置这个前缀名称的,比如我这里配置co,那么启动就是co:drive),如下:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-plugin-plugin</artifactId> <version>3.2</version> <configuration> <goalPrefix>co</goalPrefix> <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound> </configuration> <executions> <execution> <id>mojo-descriptor</id> <goals> <goal>descriptor</goal> </goals> </execution> <execution> <id>help-goal</id> <goals> <goal>helpmojo</goal> </goals> </execution> </executions> </plugin>
2、注解@Mojo是必须要的,这是定义插件对象的启动方法,由于该类只有一个方法,所以启动方法和启动类是一致的。在Maven 3之前是使用注释注解:@goal xxx这种方式。现在已经不使用这种方式了。
3、我们平时在使用Maven的各种插件的时候往往都能在配置文件中传入属性的值,比如tomcat-maven-plugin插件我们可以随意指定tomcat的端口号。这里插件的处理方式是在Car类中定义一些属性,比如下面这样。然后我们重新将插件install到本地仓库。再次运行。
@Mojo(name = "drive") public class Car extends AbstractMojo { @Parameter(defaultValue = "8080") private Integer port; @Override public void execute() throws MojoExecutionException, MojoFailureException { System.out.println("Car drive..."); System.out.println(port); } }
输出结果:
那么,在插件的配置中增加Configuration标签,加上子标签<port>,如下:
<plugin> <groupId>com.mook.plugin</groupId> <artifactId>gr-maven-plugin</artifactId> <version>1.0-RELEASE</version> <configuration> <port>8090</port> </configuration> </plugin>
那么,结果就是8090,这就是插件的参数设置方式。
10、到这里基本上就介绍完了。关于插件的运行,关系到Maven的生命周期,阶段和目标这几个概念。这是另外一个话题了。
其他:新建的eclipse项目上面报错,显示execution not covered by lifecycle这种错误,搜了一下,在<build>下<pluginManagement>下<plugins>中加入:
<?xml version="1.0" encoding="utf-8"?> <plugin> <groupId>org.eclipse.m2e</groupId> <artifactId>lifecycle-mapping</artifactId> <version>1.0.0</version> <configuration> <lifecycleMappingMetadata> <pluginExecutions> <pluginExecution> <pluginExecutionFilter> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-plugin-plugin</artifactId> <versionRange>[3.2,)</versionRange> <goals> <goal>descriptor</goal> <goal>helpmojo</goal> </goals> </pluginExecutionFilter> <action> <ignore/> </action> </pluginExecution> </pluginExecutions> </lifecycleMappingMetadata> </configuration> </plugin>
其中:groupId,artifactId,versionRange,goals是需要自己去改变的,因为不有可能其他的插件也会报这个错误,按照这几个标签内容改就行了,我这里是maven-plugin-plugin这个插件在报错。
其他:
1. 其实maven有3大类pom,超级pom,应用pom,effective pom,大致effective pom 约等于( 超级pom + 应用pom),超级pom位置在maven安装包的maven-model-builder-xxx.jar\org\apache\maven\model\pom-4.0.0.xml中,参考https://books.sonatype.com/mvnref-book/reference/pom-relationships-sect-pom.html,观察effective pom除了超级pom和应用pom有额外的插件被定义,比如clean插件,resource插件,compile插件等,他们都是maven内置插件,是根据不同的packaging类型来取舍的,当packing类型改变的时候effective pom也会跟着变化,那么这个变化来源于:maven-core-xxx.jar\META-INF\plexus\components.xml中,所以说components.xml = 超级pom + 应用pom + packaging类型所对应的插件列表。
2. 我们编写插件需要指定execution/goal,但是我们发现maven-xxx-plugin这种官方插件就不用指定,比如clean插件,当你调用mvn clean命令时候也能执行,其实并不是,我们观察effective pom就可以发现这些官方插件也都是定义了goal的,只不过是default类型的,即便我们在应用pom中进行修改,也只是增加,而无法替换调默认的goal,至于如何加上的,可以debug maven-help-plugin插件的effective-pom这个goal就可以观察到是如何生成effective pom文件的。debug的方式是<plugins>和<dependencies>中都引入插件包,<dependencies>不引入的话没有源码。在IDEA的右侧的maven工具中的plugins下找到maven-help-plugin的effective-pom这个goal进行debug即可。
2.1. 有时候我们想禁用内置插件的goal,比如:公司父pom配置了maven-source-plugin的attach-sources为jar,而自己项目配置的是jar-no-fork,那么此时其实在effective-pom中是存在2个goal的,而此时deploy会上传2次源码,如果私服不允许重复上传的话,那么此时deploy就会报错,解决办法,使父pom的goal失效,然后自定义一个自己的goal,那么此时deploy就不会上传2次源码(源码上传的次数是基于source插件生成源码的次数,生成了几次就会上传几次),例如:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>${maven-source-plugin.version}</version> <executions> <execution> <id>attach-sources</id> <phase>none</phase> </execution> <execution> <id>custom-attach-sources</id> <goals> <goal>jar-no-fork</goal> </goals> </execution> </executions> </plugin>
3. 我们自己开发插件也可以就在本项目中进行debug,而不需要install再在别的项目中来引入depency和plugin来debug,只需要在本项目中引入自己,然后在idea右侧maven工具中选中对应的插件的goal进行debug即可;
参考:http://cn.voidcc.com/question/p-vjnwwnvm-bdw.html
lombok-maven-plguin插件:
1. 使用lombok打包的源码会缺失相应完整代码,IDEA进行debug时会提示,并且debug也无法进入相关方法
2. 使用lombok-maven-plugin插件进行打包,如下所示,由于插件默认是编译src/main/lombok目录下的文件,所以修改成src/main/java,addOutputDirectory需要设置成false,如此一来插件就把完整原码添加到了target/delombok下了,而此时并没有将src/main/resources下的文件copy到delombok目录,所以使用maven-resources-plugin将配置文件也copy到delombok下,配置如下
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.16.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>delombok</goal>
</goals>
</execution>
</executions>
<configuration>
<sourceDirectory>src/main/java</sourceDirectory>
<outputDirectory>${project.build.directory}/delombok</outputDirectory>
<addOutputDirectory>false</addOutputDirectory>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>copy-to-lombok-dir</id>
<phase>process-sources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
</resources>
<outputDirectory>${project.build.directory}/delombok</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
3. 此时源码已经完整存在于delombok目录下了,还需要将原码打包成jar包,使用antrun插件打包成jar包,如下,此时target下就存在了
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>generate-delomboked-sources-jar</id>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<jar
destfile="${project.build.directory}/${project.build.finalName}-sources.jar"
basedir="${project.build.directory}/delombok"/>
</target>
</configuration>
</execution>
</executions>
</plugin>
4. 还需要将target下的源码包安装到本地mvn仓库,有3种方式,推荐使用方式2,因为maven-install-plugin是官方插件,方式1需要多输入两个单词,方式3插件是非官方的,这3种方式pom中都不需要引入maven-source-plugin(方式1可以执行因为maven-source-plugin是maven内置的)
方式1:打包命令加上maven-source-plugin,如:mvn clean source:jar install
方式2:使用maven-install-plugin插件(https://www.anycodings.com/1questions/2013029/how-to-deploy-de-lombok-ed-sources-with-maven)
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> <executions> <execution> <id>install-source-jar</id> <goals> <goal>install-file</goal> </goals> <phase>install</phase> <configuration> <file>${project.build.directory}/${project.build.finalName}-sources.jar</file> <groupId>${project.groupId}</groupId> <artifactId>${project.artifactId}</artifactId> <version>${project.version}</version> <classifier>sources</classifier> <generatePom>true</generatePom> <pomFile>${project.basedir}/pom.xml</pomFile> </configuration> </execution> </executions> </plugin>
方式3:使用build-helper-maven-plugin插件(https://stackoverflow.com/questions/52362413/create-and-install-de-lomboked-source-jar-in-maven)
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>build-helper-maven-plugin</artifactId> <version>3.0.0</version> <executions> <execution> <id>attach-source-jar</id> <phase>package</phase> <goals> <goal>attach-artifact</goal> </goals> <configuration> <artifacts> <artifact> <file>${project.build.directory}/${project.build.finalName}-sources.jar</file> <type>jar</type> <classifier>sources</classifier> </artifact> </artifacts> </configuration> </execution> </executions> </plugin>
## 禁用插件默认goal,当父类存在maven-source-plugin时,子类如果再次定义会出现重复上传的问题,可以使用如下方式禁用默认goal:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>${maven-source-plugin.version}</version> <executions> <execution> <id>attach-sources</id> <phase>none</phase> </execution> <execution> <id>custom-attach-sources</id> <goals> <goal>jar-no-fork</goal> </goals> </execution> </executions> </plugin>
## 解决一个遇到的问题:
我们的项目依赖父pom,父pom中的maven-source-plugin插件配置如下:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>3.2.0</version> <executions> <execution> <id>attach-sources</id> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin>
在我们自己的项目中又配置了此插件,如下:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>${maven-source-plugin.version}</version> <executions> <execution> <id>custom-attach-sources</id> <goals> <goal>jar-no-fork</goal> </goals> </execution> </executions> </plugin>
当执行mvn clean source:jar deploy -P xxx时,就会报错传输错误,观察控制台发现上传了两次xxx-sources.jar包(也就是源码包),错误原因就在于此,于是寻找原因,看为何会上传两次,debug插件maven-deploy-plugin发现里面的属性project的attachedArtifacts属性中包含3个一模一样的源码文件,往回找看是什么地方造成的有3个值,首先回找maven-install-plugin插件发现这里的project已经是有3个值了,再往回找maven-source-plugin插件,发现是这里添加了3个,3个分别是父pom和本项目的pom种配置的maven-source-plugin配置的<goal>加了两个,命令行source:jar又加了一个。
补充:执行mvn help:effective-pom,显示如下内容:
<plugin> <artifactId>maven-source-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <id>attach-sources</id> <goals> <goal>jar</goal> </goals> </execution> <execution> <id>custom-attach-sources</id> <goals> <goal>jar-no-fork</goal> </goals> </execution> </executions> </plugin>
这就说明:会执行两次maven-resource的goal(分别是jar和jar-no-fork),每执行一次就会在project中添加一次attachedArtifacts的值。分别在父子配置execution,如果id不同,其实相当于配了两个,子并不会覆盖父,但是如果子的id和父的id相同并且goal也想通,那么就会覆盖。可以观察mvn help:effective-pom的值。
如果id相同goal不同,会出现这种现象(2个goal靠在一起),也会执行两次:
<plugin> <artifactId>maven-source-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <id>attach-sources</id> <goals> <goal>jar-no-fork</goal> <goal>jar</goal> </goals> </execution> </executions> </plugin>
(在pom中配置和在命令行执行source:jar都会添加)所以这就是问题的根源就在于此,解决方法:
方式1: 命令行去掉source:jar,并且删掉本项目的maven-source-plugin配置,问题得到解决。
方式2:方式1的<goal>是jar,而我们希望是jar-no-fork,所以我们让父pom种的插件不生效(<phase>none</phase>)而是使用自己的custom-attach-sources,配置如下:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>${maven-source-plugin.version}</version> <executions> <execution> <id>attach-sources</id> <phase>none</phase> </execution> <execution> <id>custom-attach-sources</id> <goals> <goal>jar-no-fork</goal> </goals> </execution> </executions> </plugin>
方式3:禁用父pom和自己的pom种的配置,使用(<phase>none</phase>),而是在命令行加上source:jar
理解:由于我们打包源码,一般是要install到本地仓库,并且很大概率需要上传到私服或者中央库。那么maven-source-plugin插件最好就不配置在父pom或者自己的项目的根pom种,因为这样一来所有集成这个pom的子模块都会打包源码并且安装到本地仓库,对于不需要打包和安装和上传的模块实际上是不需要此插件的,所以maven-source-plugin插件应该是只放在需要打包的模块下,于此同时maven-install-plugin、lombok-maven-plugin也类似。
maven-antrun-plugin也会打包源码,实际上我们需要的也是此插件打包的源码(进行了delombok的源码),而maven-source-plugin又必须配置(可以不配置,在命令行加source:jar),如果不配置,那么deploy时不会上传源码(因为没有源码,所以前面的project的属性attachedArtifacts没有值,就不会有上传源码动作)。这里有一点需要注意maven-antrun-plugin需要在maven-source-plugin之后执行,才能覆盖maven-maven-plugin生成的源码。
所以总结:如果父pom存在maven-source-plugin,无论他的goal是什么,都禁用,而使用自己的jar-no-fork。命令行不使用source:jar。
有一点很疑惑:由于我们使用的源码包是maven-antrun-plugin打的,而我们可以配置maven-install-plugin如下:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-install-plugin</artifactId> <version>${maven-install-plugin.version}</version> <executions> <execution> <id>install-source-jar</id> <goals> <goal>install-file</goal> </goals> <phase>install</phase> <configuration> <file>${project.build.directory}/${project.build.finalName}-sources.jar</file> <groupId>${project.groupId}</groupId> <artifactId>${project.artifactId}</artifactId> <version>${project.version}</version> <classifier>sources</classifier> <generatePom>true</generatePom> <pomFile>${project.basedir}/pom.xml</pomFile> </configuration> </execution> </executions> </plugin>
这个配置的意思是将源码包安装到本地,这样一来我们就把所有的maven-source-plugin都禁用,那么就意味着deploy的时候也能把源码deploy上去,然而并不行。通过前面解释必须要是attachedArtifacts属性中的源码才会被deploy,而上面这种方式attachedArtifacts并没有值,所以不会deploy。也就是说必须要是maven-source-plugin打的源码才会被deploy。这里有点特殊的是:maven-source-plugin打了源码(那么就可以被deploy,因为attachedArtifacts有值),maven-antrun-plugin也打了源码,maven-antrun-plugin的源码覆盖了maven-source-plugin。所以deploy的是maven-antrun-plugin打的源码。
说明:maven-install-plugin会将jar包,源码包,pom信息都安装到本地仓库。
比较扯蛋的事:使用了3个给lombok打包源码的插件打的jar包,其他项目引入jar包,debug的时候反而会乱跳,和源码对不齐,于是乎,又把那三个插件删了
<!-- lombok拷贝文件 --> <plugin> <groupId>org.projectlombok</groupId> <artifactId>lombok-maven-plugin</artifactId> <version>${lombok-maven-plugin.version}</version> <executions> <execution> <phase>generate-sources</phase> <goals> <goal>delombok</goal> </goals> </execution> </executions> <configuration> <skip>false</skip> <sourceDirectory>src/main/java</sourceDirectory> <outputDirectory>${project.build.directory}/delombok</outputDirectory> <addOutputDirectory>false</addOutputDirectory> <encoding>UTF-8</encoding> </configuration> </plugin> <!-- resource复制到lombok包下 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>${maven-resources-plugin.version}</version> <executions> <execution> <id>copy-to-lombok-dir</id> <phase>process-sources</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <resources> <resource> <directory>${project.basedir}/src/main/resources</directory> </resource> </resources> <outputDirectory>${project.build.directory}/delombok</outputDirectory> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-antrun-plugin</artifactId> <version>${maven-antrun-plugin.version}</version> <executions> <execution> <id>generate-delomboked-sources-jar</id> <phase>package</phase> <goals> <goal>run</goal> </goals> <configuration> <target> <jar destfile="${project.build.directory}/${project.build.finalName}-sources.jar" basedir="${project.build.directory}/delombok"/> </target> </configuration> </execution> </executions> </plugin>
关于properites:
maven的pom种可以定义<properties>属性,如果需要配置jar包或者插件的属性,可以根据规则写在properties中以便简化配置,针对maven-xxx-plugin类型的官方插件比如下面的配置,可以在properties中写成: <maven.deploy.skip>true</maven.deploy.skip>:
<!-- 默认关闭部署插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <version>${maven-deploy-plugin.version}</version> <configuration> <skip>true</skip> </configuration> </plugin>
类似的还有:
<!-- 编码相关 --> <java.version>1.8</java.version> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <maven.compiler.target>${java.version}</maven.compiler.target> <maven.compiler.source>${java.version}</maven.compiler.source> <maven.compiler.compilerVersion>${java.version}</maven.compiler.compilerVersion> <project.build.sourceEncoding>${maven.compiler.encoding}</project.build.sourceEncoding> <project.reporting.outputEncoding>${project.build.sourceEncoding}</project.reporting.outputEncoding>
如果想修改父pom种的版本号(不使用父pom默认的,一般不会这么做),可以只在properties中定义版本号而不用重新再pom种定义GAV信息,找到父pom中的<properties>下的对应信息,比如父pom中定义如下:
<properties> <junit-jupiter.version>5.6.3</junit-jupiter.version> </properties>
那么,在子pom中可以如下写法,无需再次定义GAV信息:
<properties> <junit-jupiter.version>5.4.2</junit-jupiter.version> </properties>
避免这么做,这么做的原理是用子pom定义的properties去与父pom的发生冲突,使得子pom生效,这样子做虽然没问题,但是坏处是pom中几处显示值不统一,如下图,鼠标悬浮提示的版本与真实版本不一致:
** 编写的插件可以设置默认phase,比如compile,也可以在pom中配置来改变默认phase,如下:
<executions> <execution> <id>mm</id> <phase>compile</phase> <goals> <goal>mm</goal> </goals> </execution> </executions>
** 为什么不是maven-xxx-plugin的不需要配置execution,而三方插件xxx-maven-plugin不配置execution就不会生效呢,观察mvn help:effective-pom发现,其实官方插件都有配置,只不过是在超级pom中或者其他地方配置的。
** 在@Mojo的defaultPhase中默认是none,如果指定了比如compile,那么在配置插件时候可以不用指定phase,否则需要指定phase,不然就是默认none。