目录

  • 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理解的话,这里就一点就透。如果之后有分析源码的讲解,再细加分析。