目录
- Maven插件
- 常用的Maven插件
- clean
- compile
- install
- deploy
- surefire
- site
- jar
- source
- resources
- release
- 插件的定位和执行
- 如何定位插件
- Plugin management
- 插件仓库
- 扩展插件
- 初窥插件注入原理
- Plexus示例
- Guice示例
- 构建自定义插件
- 一个基础的自定义插件
- 从插件的配置获取参数
- 关于插件的执行顺序
- 总结
Maven插件
上一讲中,Maven的生命周期是由插件完成,这一讲,我们就详细了解插件的有关内容。Maven设计的优越之处是,把相关的工作委托给插件执行。插件的灵活性,使得Maven在执行特定任务时,才去下载对应插件所依赖的jar包。
本讲将涵盖以下内容,常用的几个插件介绍、插件的发现和执行过程、自定义插件。
常用的Maven插件
clean
在超级pom文件中,maven-clean-plugin的配置如下:
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>2.5</version>
<executions>
<execution>
<id>default-clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
compile
此插件有两个goal,分别对应Default生命周期的test-compile和compile阶段。
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<executions>
<execution>
<id>default-testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
<execution>
<id>default-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
默认使用JDK1.5来进行编译,可以参考以下更改对应的配置:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
以下的方式是等效的:
<configuration>
<compilerArgument>–source 1.7 –target 1.7</compilerArgument>
</configuration>
install
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>default-install</id>
<phase>install</phase>
<goals>
<goal>install</goal>
</goals>
</execution>
<execution>
<id>default-install-1</id>
<phase>install</phase>
<goals>
<goal>install</goal>
</goals>
</execution>
</executions>
</plugin>
deploy
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<id>default-deploy</id>
<phase>deploy</phase>
<goals>
<goal>deploy</goal>
</goals>
</execution>
</executions>
</plugin>
远程的仓库的配置在后续会讲到。
surefire
这是一个测试插件,绑定的是test阶段。
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<executions>
<execution>
<id>default-test</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
<execution>
<id>default-test-1</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>
虽然这是在超级pom中有的,但是你需要在项目的pom文件引入所依赖的测试jar(当然按需要,你可以改为TestNG对应的jar),因为这些是你编写测试代码所必须的:
<dependencies>
[...]
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
[...]
</dependencies>
该插件还引入了测试提供方(test provider)的概念,也就是maven在测试阶段运行那些测试框架。一般会依据classpath出现的JUnit或TestNG版本自动选择测试提供方,但你也可以改写配置来直接指定。但这仍是需要先引入正确的测试Jar。详细了解可参考surefire 官方文档。
<plugins>
[...]
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.17</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.surefire</groupId>
<artifactId>surefire-junit47</artifactId>
<version>2.17</version>
</dependency>
</dependencies>
</plugin>
[...]
</plugins>
site
该插件有8个goal:
- site:site: 生成site
- site:deploy: 根据distributionManagement 的URL配置,部署生成的site
- site:run: 使用Jetty启动
- site:stage: 基于distributionManagement 的URL,在本地生成一个暂存的或mock目录
- site:stage-deploy: 将site部署到上面的stage目录
- site:attach-descriptor: 生成site描述文件site.xml
- site:jar:
- site:effective-site: 类似于effective-pom
超级pom文件中的配置如下,site和deploy目标分别对应生命周期的site和site-deploy阶段。
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.3</version>
<executions>
<execution>
<id>default-site</id>
<phase>site</phase>
<goals>
<goal>site</goal>
</goals>
<configuration>
<outputDirectory>PROJECT_HOME/target/site</outputDirectory>
<reportPlugins>
<reportPlugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>
maven-project-info-reports-plugin
</artifactId>
</reportPlugin>
</reportPlugins>
</configuration>
</execution>
<execution>
<id>default-deploy</id>
<phase>site-deploy</phase>
<goals>
<goal>deploy</goal>
</goals>
<configuration>
<outputDirectory>PROJECT_HOME/target/site</outputDirectory>
<reportPlugins>
<reportPlugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>
maven-project-info-reports-plugin
</artifactId>
</reportPlugin>
</reportPlugins>
</configuration>
</execution>
</executions>
<configuration>
<outputDirectory>PROJECT_HOME/target/site</outputDirectory>
<reportPlugins>
<reportPlugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>
maven-project-info-reports-plugin
</artifactId>
</reportPlugin>
</reportPlugins>
</configuration>
</plugin>
jar
超级pom的配置如下,多数时候不需要重写。如果你需要创建一个可自执行的jar文件,可以参考以下 内容。
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>default-jar</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
<execution>
<id>default-jar-1</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
source
该插件包含5个目标(goals):aggregate、jar、test-jar、jar-no-fork和test-jar-no-fork,这些在生命周期的package阶段都会执行。和之前几个不同,这个插件在超级pom中没有提供,需要在自己的项目下定义:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.3</version>
<configuration>
<outputDirectory>
/absolute/path/to/the/output/directory
</outputDirectory>
<finalName>filename-of-generated-jar-file</finalName>
<attach>false</attach>
</configuration>
</plugin>
</plugins>
</build>
...
</project>
这个和jar插件的不同是,这个是把源码打成jar包。
resources
该插件把相应的资源文件输出到指定目录下。resources目标复制main下面的资源,对应生命周期的process-resources阶段;testResources目标处理test相关的资源,对应生命周期的process-test-resources阶段。
超级pom文件中的配置如下,如果需要过滤特定的资源文件,可参考这里:
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>default-resources</id>
<phase>process-resources</phase>
<goals>
<goal>resources</goal>
</goals>
</execution>
<execution>
<id>default-testResources</id>
<phase>process-test-resources</phase>
<goals>
<goal>testResources</goal>
</goals>
</execution>
<execution>
<id>default-resources-1</id>
<phase>process-resources</phase>
<goals>
<goal>resources</goal>
</goals>
</execution>
<execution>
<id>default-testResources-1</id>
<phase>process-test-resources</phase>
<goals>
<goal>testResources</goal>
</goals>
</execution>
</executions>
</plugin>
release
该插件也包含8个目标:
- release:clean:
- release:prepare:
- release:prepare-with-pom: 生成一个处理好依赖的pom文件
- release:rollback: 回滚前面的作为
- release:perform:
- release:stage:
- release:branch:
- release:update-versions:
其中release:prepare目标涉及的工作内容有:
- 检验所有源码都已提交
- 确保所有依赖没有快照版本
- 更改pom文件的版本号,比如去掉snapshot
- pom文件的SCM信息改为最终标签的路径
- 基于更改后的pom文件,执行所有的测试
- 提交pom文件的更改,为当前的代码版本打上标签
- 更改主干项目的版本号,向下一个版本号推进
这个和source插件一样,超级pom文件没有对应的配置,需要在项目下指定:
<plugin>
<artifactId>maven-release-plugin</artifactId>
<version>2.5</version>
<configuration>
<releaseProfiles>release</releaseProfiles>
<goals>deploy assembly:single</goals>
</configuration>
</plugin>
插件的定位和执行
如何定位插件
Maven定位插件也是依据三要素:groupId、artifactId、version。但是通过以上的各个配置,没有groupId。当没有这个groupId配置时,maven会先从settings.xml 文件的以下配置找:
<pluginGroups>
<pluginGroup>com.packt.plugins</pluginGroup>
</pluginGroups>
然后再去repository/org/apache/maven/plugins/ 中去找,最后是 repository/org/codehaus/mojo/ 。当然可以显式地在plugin标签下指明groupId:
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
...
</plugin>
Plugin management
这个第一讲有提到,这个是在父pom文件统一配置,起到统一版本的作用。
<pluginManagement>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.5</version>
<extensions>true</extensions>
</plugin>
</pluginManagement>
插件仓库
当Maven从本地仓库查找对应的插件jar包找不到就会从超级pom文件中的配置仓库下载。可以在项目的pom文件重写这个属性:
<pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Maven Plugin Repository</name>
<url>http://repo1.maven.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<updatePolicy>never</updatePolicy>
</releases>
</pluginRepository>
</pluginRepositories>
扩展插件
如果关联到项目的插件引入了新的打包类型,或者是自定义生命周期使用的插件,需要设置extensions为true 。只有这样Maven才会去 components.xml 文件找相应的插件jar包。
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
</plugin>
初窥插件注入原理
在2002年的时候,Maven已经出现了,但是那时还没有Spring。本讲开头说Maven的设计哲理之一是把任务委托插件执行,因此需要一种Ioc(Inverse of control)框架组件,来把插件注入容器中。当时一个是Plexus可以提供Maven的需要,我们看到的多是Plexus的配置。但是Plexus使用自己的规则。后来JSR提出DI的标准,Google旗下的Guice也提供了Ioc功能,Maven3后,就也支持Guice的一套配置了。
Plexus示例
// 接口
package com.packt.di;
public interface MessagingService {
public void sendMessage(String recipient, String message);
}
// 两个实现
package com.packt.di;
public class SMSMessagingService implements MessagingService {
@Override
public void sendMessage(String recipient, String message) {
System.out.println("SMS sent to : " + recipient);
}
}
public class EmailMessagingService implements MessagingService {
@Override
public void sendMessage(String recipient, String message) {
System.out.println("Email sent to : " + recipient);
}
}
配置文件:
<component-set>
<components>
<component>
<role>com.packt.di.MessagingService</role>
<role-hint>sms</role-hint>
<implementation>
com.packt.di.SMSMessagingService
</implementation>
</component>
<component>
<role>com.packt.di.MessagingService</role>
<role-hint>email</role-hint>
<implementation>
com.packt.di.EmailMessagingService
</implementation>
</component>
</components>
</component-set>
使用方
package com.packt.di;
import org.codehaus.plexus.DefaultPlexusContainer;
import org.codehaus.plexus.PlexusContainer;
public class MessageSender {
public static void main(String[] args) {
PlexusContainer container = null;
MessagingService msgService = null;
try {
container = new DefaultPlexusContainer();
// send SMS
msgService = (MessagingService)
container.lookup(MessagingService.class, "sms");
msgService.sendMessage("+94718096732", "Welcome to
Plexus");
// send Email
msgService = (MessagingService)
container.lookup(MessagingService.class, "email");
msgService.sendMessage("prabath@apache.org", "Welcome to
Plexus");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (container != null) {
container.dispose();
}
}
}
}
可以看出,role就是接口的类型,如果只有一个实现,就不需要role-hint标签了。多个实现才需要这个标签定位。Maven定位插件类也是类似的方式,可能通过 container.lookup() 方法。
Guice示例
Guice不是通过XML配置,而是使用配置类,绑定具体实现和接口的关系。
package com.packt.di;
import javax.inject.Inject;
public class GuiceMessageSender {
private MessagingService messagingService;
@Inject
public void setService(MessagingService
messagingService) {
this.messagingService = messagingService;
}
public void sendMessage(String recipient, String
message) {
messagingService.sendMessage(recipient, message);
}
}
import com.google.inject.AbstractModule;
public class GuiceInjector extends AbstractModule {
@Override
protected void configure() {
bind(MessagingService.class).
to(SMSMessagingService.class);
}
}
import com.google.inject.Guice;
import com.google.inject.Injector;
public class GuiceClientApplication {
public static void main(String[] args) {
Injector injector;
GuiceMessageSender messageSender;
injector = Guice.createInjector(new GuiceInjector());
messageSender = injector.getInstance(GuiceMessageSender.class);
messageSender.sendMessage("+94718096732", "Welcome to Plexus");
}
}
构建自定义插件
一个基础的自定义插件
如果我们需要自定义一个插件,在构建完成时,发送信息给指定的接收人。我们以此示例,跟踪下面的全流程,了解构建自定义插件的每个步骤:
1. 对于goal需要写一个MOJO(MOJO,Maven plain Old Java Object,就是插件goal的实现)。注意注解里面的值是插件goal的名称。
package com.packt.plugins;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
@Mojo( name = "mail")
public class EmailMojo extends AbstractMojo {
public void execute() throws MojoExecutionException {
getLog().info( "Sending Email…" );
}
}
2. 接下来,我们需要把这个goal放到插件中,我们需要一个pom文件,把代码打包成插件:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.plugins</groupId>
<artifactId>mail-maven-plugin</artifactId>
<version>1.0.0</version>
<packaging>maven-plugin</packaging>
<name>PACKT Maven Plugin Project</name>
<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>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.2</version>
<configuration>
<skipErrorNoDescriptorsFound>
true
</skipErrorNoDescriptorsFound>
</configuration>
<executions>
<execution>
<id>mojo-descriptor</id>
<goals>
<goal>descriptor</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
3. 我们查看jar包中的 /META-INF/-maven/plugin.xml 内容如下:
<plugin>
<name>PACKT Maven Plugin Project</name>
<description></description>
<groupId>com.packt.plugins</groupId>
<artifactId>mail-maven-plugin</artifactId>
<version>1.0.0</version>
<goalPrefix>mail</goalPrefix>
<isolatedRealm>false</isolatedRealm>
<inheritedByDefault>true</inheritedByDefault>
<mojos>
<mojo>
<goal>mail</goal>
<requiresDirectInvocation>false
</requiresDirectInvocation>
<requiresProject>true</requiresProject>
<requiresReports>false</requiresReports>
<aggregator>false</aggregator>
<requiresOnline>false</requiresOnline>
<inheritedByDefault>true</inheritedByDefault>
<implementation>com.packt.plugins.EmailMojo
</implementation>
<language>java</language>
<instantiationStrategy>per-lookup
</instantiationStrategy>
<executionStrategy>once-per-session
</executionStrategy>
<threadSafe>false</threadSafe>
<parameters/>
</mojo>
</mojos>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<type>jar</type>
<version>2.0</version>
</dependency>
</dependencies>
</plugin>
对其中的标签解释。
- goal goal的名称
- requiresDirectInvocation 标识是否必须直接调用。设置为true,就不能关联到某个生命周期,通过生命周期的阶段来调用了。
- requiresProject 是否是Maven项目。设置为true,就需要基于pom.xml执行。
- requiresReports
- aggregator
- requiresOnline 是否是需要线上构建。
- instantiationStrategy MOJO的实例化策略,per-lookup标识每次都创建新的实例。其它可选的值有:keep-alive、
singleton、 poolable - executionStrategy 执行策略,once-per-session和always
- threadSafe 是否需要线程安全的构建
- inheritByDefault 插件的goal是否被子项目继承
- goalPrefix maven的插件执行命令是 > mvn goalPrefi:goal,对于插件就是 mvn mail:mail。这个是取artifactId的第一个连字符前的字符串,如果我们想自定义,可以在插件的pom文件maven-plugin-plugin的属性加上以下配置:
<configuration>
<goalPrefix>email</goalPrefix>
</configuration>
这些配置也可以在注解上指定:
@Mojo(name = "mail", requiresDirectInvocation = false,
requiresProject = true, requiresReports = false,
aggregator = true, requiresOnline = true,
inheritByDefault = true, instantiationStrategy =
InstantiationStrategy.PER_LOOKUP, executionStrategy =
"once-per-session", threadSafe = false)
4. 因为这个是自定义插件,还需要配置maven的pluginGroup。
<pluginGroup>com.packt.plugins</pluginGroup>
5. 有了插件,接下来就是在生命周期的阶段绑定插件的goal:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.plugins</groupId>
<artifactId>plugin-consumer</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>PACKT Maven Plugin Consumer Project</name>
<build>
<plugins>
<plugin>
<groupId>com.packt.plugins</groupId>
<artifactId>mail-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<id>post-integration-mail</id>
<phase>post-integration-test</phase>
<goals>
<goal>mail</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
如果这个插件对应的阶段固定的话,也可以采用以下的方式,使用注解@Execute:
@Mojo( name = "mail", requiresProject = false)
@Execute (phase = LifecyclePhase.POST_INTEGRATION_TEST)
public class EmailMojo extends AbstractMojo {
}
此时xml中就是需要指定插件的坐标就可以了
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.plugins</groupId>
<artifactId>plugin-consumer</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>PACKT Maven Plugin Consumer Project</name>
<build>
<plugins>
<plugin>
<groupId>com.packt.plugins</groupId>
<artifactId>mail-maven-plugin</artifactId>
<version>1.0.0</version>
</plugin>
</plugins>
</build>
</project>
从插件的配置获取参数
我们可能需要获取收件人的信息,以下从Maven项目获取所有插件,在获取configuration信息。
@Mojo(name = "mail")
public class EmailMojo extends AbstractMojo {
@Component
private MavenProject project;
public void execute() throws MojoExecutionException {
// get all the build plugins associated with the
// project under the build.
List<Plugin> plugins = project.getBuildPlugins();
if (plugins != null && plugins.size() > 0) {
for (Iterator<Plugin> iterator = plugins.iterator();
iterator.hasNext();) {
Plugin plugin = iterator.next();
// iterate till we find mail-maven-plugin.
if ("mail-maven-plugin".equals(plugin.getArtifactId())) {
getLog().info(plugin.getConfiguration().toString());
break;
}
}
}
}
}
引用插件的xml中configuration位置如下:
<build>
<plugins>
<plugin>
<groupId>com.packt.plugins</groupId>
<artifactId>mail-maven-plugin</artifactId>
<version>1.0.0</version>
<configuration>
<emailList>
prabath@wso2.com,
prabath@apache.org
</emailList>
<mailServer>mail.google.com</mailServer>
<password>password</password>
</configuration>
<executions>
<execution>
<id>post-integration-mail</id>
<phase>post-integration-test</phase>
<goals>
<goal>mail</goal>
</goals
</execution>
</executions>
</plugin>
</plugins>
</build>
多说一点,通过注入的MavenProject,还可以获取当前项目的坐标3要素:
getLog().info("Artifact Id " + project.getArtifactId());
getLog().info("Version " + project.getVersion());
getLog().info("Packaging " + project.getPackaging());
关于插件的执行顺序
各插件在生命周期的执行顺序是由阶段控制的,阶段是有序的。但是如果同一个阶段有多个goal,那么执行的顺序是pom文件定义的executions中的顺序。
总结
本讲围绕Maven插件,介绍了几个常见的、公有的官方(或可靠第三方)插件,接着介绍了插件的定位方式和执行原理,最后讲如何自定义插件及使用自定义插件。原理的部分没有往细节,如果对Spring的IoC理解的话,这里就一点就透。如果之后有分析源码的讲解,再细加分析。