别再只会 mvn install 了!深入拆解 Maven 插件核心原理

一、Maven与Maven插件

确切的说,我们日常提及的"Maven"实际上只是Maven插件集合的核心框架。而插件才是执行实际操作的东西,插件可以用于:创建jar文件、创建war文件、编译代码、单元测试代码、创建项目文档等等。

几乎所有您能想到的在项目中执行的操作都是通过Maven插件实现的。而我们安装的maven仅仅是为插件的运行提供一个管理框架

我们在打开一个maven管理的工程时,通常能在其POM文件中看到这样的内容

xml 复制代码
<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有自己的标准插件,所以即使一个项目没有显式的引用Maven插件,也能执行一些基础的mvn命令,如clean、compile等。但是这个时候是默认配置。

所以如果你想有自己的配置,或者是有特定的目标,就需要显示的指定与配置plugins 了。

二、maven 插件使用举例

1. flatten-maven-plugin

① 插件配置

以我们一个实际项目为例,由于项目比较大,而且多模块互相引用,所以使得jar包也嵌套层数过高,那么工程版本管理就很繁琐,这个时候我们就可以使用 flatten-maven-plugin 这个插件,这个插件的作用是将项目的依赖打平(flatten),将所有依赖的版本号都解析并嵌入到POM文件中

xml 复制代码
<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} 这种占位符,可以在mvn命令中直接使用mvn -Drevision=2.0.0-SNAPSHOT clean package,来在命令中为这些占位符赋值,并进行后续操作
  • updatePomFile: 表示会更新生成的扁平化 POM 文件
  • ignoreTransitiveDependencies: 忽略传递依赖,确保这些依赖不被包含在最终的扁平化 POM 中

除此以外,我们还定义了两个 execution,分别用于执行 flatten 以及 clean 操作。这样配置后,当执行 mvn generate-resources 时,会触发 flatten 操作,将项目的依赖进行扁平化处理。而执行 mvn clean 时,会执行 clean 操作,清理生成的扁平化文件。

② 作用

当我们经过上述配置后,当我们进行打包等操作时,触发 generate-resources 步骤,就能正常运行的插件了。最终的结果就是在我们指定的目录下,产生了一个.flattened-pom.xml

图片

图片

而且其内部就是jar包的平铺形式

2. exec-maven-plugin

① 插件配置

这个插件要用于在构建过程中执行外部程序或脚本。通过这个插件,可以方便地在Maven项目构建过程中调用外部命令、执行Shell脚本等。一个最常见的作用就是,进行代码混淆

xml 复制代码
<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><!-- 指定执行的命令为java -->
                        <arguments>
                            <argument>-jar</argument>
                            <argument>path/to/allatori.jar</argument><!-- Allatori的jar包路径 -->
                            <argument>-config</argument>
                            <argument>path/to/allatori-config.xml</argument><!-- Allatori混淆配置文件路径 -->
                            <argument>-in</argument>
                            <argument>${project.build.directory}/${project.build.finalName}.jar</argument><!-- 待混淆的jar包路径 -->
                            <argument>-out</argument>
                            <argument>${project.build.directory}/${project.build.finalName}-obfuscated.jar</argument><!-- 混淆后的jar包输出路径 -->
                        </arguments>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

涉及的各个具体配置的含义如下:

  • <executable>:指定要执行的命令为java,表示使用Java来执行命令。

  • <arguments>:配置执行的命令参数,具体含义如下:

    • -jar path/to/allatori.jar:指定执行的jar包为Allatori混淆工具的jar包路径。
    • -config path/to/allatori-config.xml:指定Allatori混淆工具所需的配置文件路径。
    • -in ${project.build.directory}/${project.build.finalName}.jar:指定待混淆的jar包路径。
    • -out ${project.build.directory}/${project.build.finalName}-obfuscated.jar:指定混淆后的jar包输出路径。

通过这样的配置,项目构建过程中会在package阶段执行命令,使用java -jar 这样的命令调用Allatori混淆工具,对我们的工程打包结果进行混淆处理。不难发现,这里其实还有一个关键的配置文件 allatori-config.xml

图片

关于这个配置文件该怎么配,他的属性与作用,可以见官方文档: https://allatori.com/doc.html ,我们就不再赘述

② 作用

经过配置后,我们的编译的代码就会产生一些变化,比如变量名变成了VAR系列,当然,变化的多寡取决于我们上面的配置文件

图片

图片

当然,这里还有一个很特殊的点。就是使用 allatori 会有一个默认配置 synthetize-methods, 这个默认配置会让所有私有方法被标记为合成方法。而很多反编译器不展示合成方法,导致的结果就是反编译后看不到任何 private 方法

图片

三、maven 插件原理

1. MOJO(Maven Plain Old Java Object)

谈及maven插件,就必须提及一个重要概念MOJO ,MOJO 是一个缩写,全称为 Maven Plain Old Java Object。MOJO 是 Maven 插件中的一个核心概念,它代表了一个具体的构建任务或目标(goal)。

每个 MOJO 都是一个 Java 类,实现了特定的构建逻辑,并且可以通过 Maven 的命令行或配置文件(如 pom.xml)来调用。

MOJO 的主要特点

Java 类:

MOJO 是一个普通的 Java 类,通常继承自 org.apache.maven.plugin.AbstractMojo 类。

这个类提供了许多有用的方法和属性,帮助你更方便地编写插件。

目标(Goal):

每个 MOJO 定义了一个或多个目标(goal),这些目标可以在 Maven 的生命周期中被调用。

目标是一个具体的构建任务,例如编译代码、打包、测试等。

注解:

MOJO 类和方法通常使用注解来提供元数据,例如 @Mojo 注解用于标记一个类为 MOJO,@Parameter 注解用于标记类的属性为插件配置参数。

配置参数:

MOJO 可以通过 pom.xml 或命令行接收配置参数,这些参数用于控制 MOJO 的行为。

例如, 元素可以用来配置 MOJO 的参数。

2. 通用的配置

知道了Mojo后,我们再回头看看插件,插件由一个或多个Mojo组成,其中一个Mojo映射到一个目标。

例如,您有一个Mojo,它对特定的URL执行查询,并带有指定的超时和选项列表。Mojo可能是这样的

kotlin 复制代码
@Mojo( name = "query" )
publicclass MyQueryMojo
    extends AbstractMojo
{
    @Parameter(property = "query.url", required = true)
    private String url;
    
    @Parameter(property = "timeout", required = false, defaultValue = "50")
    privateint timeout;
    
    @Parameter(property = "options")
    private String[] options;

@Override
    public void execute()
        throws MojoExecutionException
    {
        ...
    }
}

然后再要使用这个插件的位置,这样设置pom.xml

xml 复制代码
<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中字段的名称相匹配。映射是直接的。url元素映射到url字段,timeout元素映射到timeout字段,options元素映射到options字段。映射机制可以通过检查字段的类型并确定是否可能有合适的映射来处理数组。

四、自定义Maven插件

1. 编写一个maven插件

比如我们想写一个名字叫 "hello-maven-plugin" 的maven插件,目的是提供一个sayHi方法,在编译时输出一个Hello, world。那么我们首先创建一个maven项目,然后这样设置pom文件

xml 复制代码
<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 on annotations -->
        <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

scala 复制代码
package org.example;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
/**
 * Says "Hi" to the user.
 *
 */
@Mojo(name = "sayhi")
public class GreetingMojo extends AbstractMojo
{
    @Override
    public void execute() throws MojoExecutionException
    {
        getLog().info("Hello, world.");
    }
}

完成上述后,我们进行一次mvn install,把我们自己编写的这个插件上传到本地仓库中,以便被其他项目使用。

2. 直接执行

继上述我们写完自己的插件,并且install后,我们就能在其他项目里运用我们自己的这个插件了。比如我现在有一个项目,我在其pom文件里加上我们自己配置的插件

图片

然后我们就看到我们这个项目可以使用自定义插件的功能了

图片

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

图片

3. 绑定周期

但是大部分时候,我们不会手动去执行 mvn sample.plugin:hello-maven-plugin:1.0-SNAPSHOT:sayhi 这种命令,而是希望在某个通用的动作时,他能够自动去执行。

比方说我们希望在清理编译结果之前他可以自动sayhI, 这个时候我们就可以写成这样

图片

那么当我们运行clean的时候,就能看到他在clean之前,自动执行了sayhi

相关推荐
百***49002 小时前
SpringSecurity的配置
java
@老蝴2 小时前
Java EE - 常见的死锁和解决方法
java·开发语言·java-ee
wangmengxxw2 小时前
Swagger技术
java·swagger
全干engineer3 小时前
idea拉取github代码 -TLS connect error 异常问题
java·github·intellij-idea
10岁的博客3 小时前
二维差分算法高效解靶场问题
java·服务器·算法
百***93503 小时前
Tomcat报404问题解决方案大全(包括tomcat可以正常运行但是报404)
java·tomcat
qq_281317473 小时前
kubernetes(k8s)-pod生命周期
java·容器·kubernetes
IT界的奇葩3 小时前
代码规范 spring-javaformat使用
java·spring·代码规范
披着羊皮不是狼4 小时前
多用户跨学科交流系统(4)参数校验+分页搜索全流程的实现
java·spring boot