一、Maven与Maven插件
在日常开发中,我们常说的"Maven"实际上指的是其核心框架,它主要负责管理插件的执行。而真正执行具体操作的是各种Maven插件,这些插件可以用于创建JAR文件、编译代码、运行单元测试、生成项目文档等任务。
几乎所有项目构建相关的操作都是通过Maven插件来实现的。我们安装的Maven环境只是为插件的运行提供了一个管理框架。
在打开Maven项目时,通常能在POM文件中看到以下配置:
<project>
<!-- 其他配置 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>
其中plugins部分就是Maven插件的配置。Maven提供了一系列标准插件,因此即使项目没有显式声明插件,也能执行clean、compile等基础命令,不过这些操作使用的是默认配置。
如果需要进行个性化配置或实现特定功能,就需要显式地声明和配置plugins。
二、Maven插件使用实例
1. flatten-maven-plugin
① 插件配置
在多模块项目中,当模块间依赖关系复杂、嵌套层次过深时,版本管理会变得相当繁琐。这时可以使用flatten-maven-plugin插件,它的作用是将项目依赖进行扁平化处理,将所有依赖版本号解析并嵌入到POM文件中。
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.2.5</version>
<configuration>
<outputDirectory>${project.build.directory}</outputDirectory>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
<updatePomFile>true</updatePomFile>
<ignoreTransitiveDependencies>true</ignoreTransitiveDependencies>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>generate-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
配置参数说明:
- outputDirectory:指定扁平化后的POM文件输出目录
- flattenMode:设置扁平化模式为resolveCiFriendliesOnly,用于处理${revision}、${sha1}、${changelist}等占位符
- updatePomFile:更新生成的扁平化POM文件
- ignoreTransitiveDependencies:忽略传递依赖,确保这些依赖不包含在最终的扁平化POM中
配置中还定义了两个execution,分别用于执行flatten和clean操作。当执行mvn generate-resources时触发flatten操作,执行mvn clean时清理生成的扁平化文件。
② 功能效果
完成上述配置后,在打包过程中触发generate-resources阶段,插件就会正常执行。最终在指定目录下生成.flattened-pom.xml文件,其中包含了所有依赖的平铺形式展示。

2. exec-maven-plugin
① 插件配置
这个插件主要用于在构建过程中执行外部程序或脚本,可以方便地在Maven项目构建过程中调用外部命令、执行Shell脚本等。一个典型应用场景是代码混淆。
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>obfuscate</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>java</executable>
<arguments>
<argument>-jar</argument>
<argument>path/to/allatori.jar</argument>
<argument>-config</argument>
<argument>path/to/allatori-config.xml</argument>
<argument>-in</argument>
<argument>${project.build.directory}/${project.build.finalName}.jar</argument>
<argument>-out</argument>
<argument>${project.build.directory}/${project.build.finalName}-obfuscated.jar</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
配置参数详解:
<executable>:指定执行的命令为java
<arguments>:配置执行命令的参数
- -jar path/to/allatori.jar:指定Allatori混淆工具的JAR包路径
- -config path/to/allatori-config.xml:指定混淆配置文件路径
- -in参数:指定待混淆的JAR包路径
- -out参数:指定混淆后的JAR包输出路径
通过这样的配置,在package阶段会执行命令调用Allatori混淆工具对工程打包结果进行处理。这里还需要关键的配置文件allatori-config.xml。

关于配置文件的详细属性和用法,可以参考官方文档:https://allatori.com/doc.html
② 功能效果
配置完成后,编译代码会发生相应变化,比如变量名被替换为VAR系列。具体变化程度取决于配置文件的设置。

需要注意的是,allatori有一个默认配置synthetize-methods,会将所有私有方法标记为合成方法。由于很多反编译器不展示合成方法,导致反编译后看不到任何private方法。

三、Maven插件原理
1. MOJO(Maven Plain Old Java Object)
MOJO是Maven插件中的核心概念,全称为Maven Plain Old Java Object。每个MOJO代表一个具体的构建任务或目标(goal)。
每个MOJO都是一个Java类,实现特定的构建逻辑,可以通过Maven命令行或POM配置文件调用。
MOJO的主要特性
Java类结构:
MOJO是一个普通的Java类,通常继承自org.apache.maven.plugin.AbstractMojo类,该类提供了许多有用的方法和属性。
构建目标:
每个MOJO定义一个或多个目标,这些目标可以在Maven生命周期中被调用,执行如编译代码、打包、测试等具体任务。
注解支持:
MOJO类和方法使用注解提供元数据,如@Mojo注解标记类为MOJO,@Parameter注解标记类的属性为插件配置参数。
参数配置:
MOJO可以通过pom.xml或命令行接收配置参数,使用<configuration>元素配置MOJO参数。
2. 通用配置机制
MOJO组成了插件的核心,每个MOJO映射到一个具体目标。例如,一个执行URL查询的MOJO可能如下定义:
@Mojo(name = "query")
public class MyQueryMojo extends AbstractMojo {
@Parameter(property = "query.url", required = true)
private String url;
@Parameter(property = "timeout", required = false, defaultValue = "50")
private int timeout;
@Parameter(property = "options")
private String[] options;
@Override
public void execute() throws MojoExecutionException {
// 执行逻辑
}
}
在POM文件中的配置方式:
<project>
...
<build>
<plugins>
<plugin>
<artifactId>maven-myquery-plugin</artifactId>
<version>1.0</version>
<configuration>
<url>http://www.foobar.com/query</url>
<timeout>10</timeout>
<options>
<option>one</option>
<option>two</option>
<option>three</option>
</options>
</configuration>
</plugin>
</plugins>
</build>
...
</project>
配置元素与MOJO中字段名称直接映射,映射机制通过检查字段类型自动处理数组等复杂结构。
四、自定义Maven插件开发
1. 编写Maven插件
假设我们要开发一个名为"hello-maven-plugin"的插件,提供sayHi方法在编译时输出"Hello, world"。首先创建Maven项目,配置POM文件:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>sample.plugin</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>maven-plugin</packaging>
<name>Sample Parameter-less Maven Plugin</name>
<properties>
<maven-plugin-tools.version>3.15.1</maven-plugin-tools.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.9.9</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>${maven-plugin-tools.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>${maven-plugin-tools.version}</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
核心实现类GreetingMojo.java:
package org.example;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
@Mojo(name = "sayhi")
public class GreetingMojo extends AbstractMojo {
@Override
public void execute() throws MojoExecutionException {
getLog().info("Hello, world.");
}
}
完成代码编写后,执行mvn install将插件安装到本地仓库,供其他项目使用。
2. 直接执行插件
安装完成后,在其他项目的POM文件中配置自定义插件:

配置后即可使用自定义插件功能:

双击hello.sayhi或执行命令:mvn sample.plugin:hello-maven-plugin:1.0-SNAPSHOT:sayhi

3. 绑定生命周期
通常我们不会手动执行复杂命令,而是希望插件在特定阶段自动执行。例如,希望在清理编译结果前自动执行sayhi,可以这样配置:

运行clean时,会在clean之前自动执行sayhi:

通过自定义Maven插件的开发,可以极大地扩展Maven的功能,满足特定项目的构建需求。