ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从零手写Maven插件:构建自动化与自定义Mojo实战指南

从零手写Maven插件:构建自动化与自定义Mojo实战指南 说实话最开始接触 Maven 插件开发时我也觉得这东西离普通开发者很远像是框架作者才需要碰的领域。直到有一次我在一个十几个子模块的项目里需要自动生成接口清单文档IDE 手动导出太繁琐Shell 脚本又拿不到完整的 Maven 依赖树信息折腾一圈下来最后的选择就是手写一个 Maven 插件。这篇内容就是我从零手写 Maven 插件的完整过程适合任何用过 Maven、被重复构建步骤折磨过、想给团队造点内部小工具的开发者我会从为什么要写讲到怎么写再到调试排错和工程化落地尽量把能踩的坑都提前标注出来。1. 为什么你会需要手写一个 Maven 插件三个真实场景1.1 场景一构建后自动生成接口清单文档我最早遇到的真实需求就是接口文档生成。当时项目用的是 Spring Boot各模块对外提供的 REST 接口散落在几十个 Controller 里每次版本发布前都要人工整理接口路径、入参出参、负责人又慢又容易漏。IDE 虽然有导出功能但没法直接在打包时自动化执行。有了 Maven 插件之后就不一样了你可以把这段逻辑写成插件绑定到 compile 或者 package 阶段再结合反射或者源码解析扫描 Controller构建结束的同时文档就生成到了 target 目录发版前只需要把文档拷出来。这就是插件最典型的应用场景——把构建过程中的自定义动作变成标准化产物。1.2 场景二代码规范与关键信息校验第二个常见场景是规范校验。像 Checkstyle、PMD 这类现成插件已经覆盖了大部分通用检查但很多团队的规范是业务级别的比如所有对外 API 必须包含接口描述注解新增的公共类必须有作者信息禁止在 Controller 中直接使用 MyBatis 的 Mapper。这种规则用现成插件很难低成本落地自己写一个校验型插件反而简单扫描源码或字节码发现违规直接让构建失败警告写得清清楚楚哪个文件哪一行违反了哪条规则。相比在 Code Review 阶段靠人肉盯这种在 CI 流水线里自动拦截的方案明显更靠谱。1.3 场景三多模块项目里的批量装配与资源处理第三个场景处理的是多模块项目的批量化问题。比如你有一堆子模块每个模块的 src/main/resources 下都需要生成一份包含模块名、版本号、构建时间的 metadata 文件又比如你想在 install 之后把所有模块的 jar 包统一归档到一个约定的发布目录。这些操作用脚本写跨平台一塌糊涂路径分割符不一致、编码问题层出不穷而且脚本拿不到 Maven 内部的坐标和依赖信息。插件方案就没有这些问题插件直接用 Java 写天然跨平台Maven 运行时会帮你把项目坐标、依赖列表、构建目录这些信息准备好你只需要在 Mojo 里取用即可。一句话总结需要依赖 Maven 上下文信息的自动化操作都值得写成插件而不是脚本。1.4 为什么不建议直接用 Shell 脚本或 CI 命令来替代很多人会问我直接在 Jenkins 流水线里多写几步 sh不是也能实现吗可以但有三个绕不开的痛点。第一脚本拿不到 Maven 的运行时模型。你在脚本里很难方便地获得当前模块的 artifactId、完整依赖树、各阶段执行顺序。当然可以解析 pom.xml但一个多模块项目里各种 parent、property、profile 嵌套解析出来的结果跟 Maven 内部模型经常有偏差这种偏差引发的怪问题相当折磨人。第二脚本的可复用性差。团队换人、电脑换环境脚本八成跑不起来插件打包成构件之后只要公司私服里能拉到坐标任何人在任何机器上都能用同一套命令执行。第三脚本难以与生命周期集成。绑定到指定 phase、参与构建顺序、失败时中断构建这些是 Maven 的天然特性脚本做起来就别扭了。我自己的体会是第一次写插件可能要花半天但换来的是一劳永逸的自动化能力这笔账很划算。2. 拆开 Maven 插件的壳生命周期、Phase 与 Mojo 到底怎么协作在动手写代码之前一定要把 Maven 插件最基本的运行机制搞明白否则很容易陷入照着教程写出来了但一改就挂的状态。2.1 生命周期Lifecycle、阶段Phase、目标Goal的关系Maven 构建本质上是一条流水线流水线本身是生命周期。最常用的是 default 生命周期它定义了从 validate 到 deploy 的一长串有序阶段validate、compile、test、package、verify、install、deploy 等等。每个阶段是一个时间节点节点之间严格有序前一步没执行完后一步不会开始。插件是流水线上的功能模块而插件里的每个 Mojo 定义了模块里的一个具体动作Maven 里叫 Goal。比如 maven-compiler-plugin 里的 compile goal就是编译主代码这个动作。这三者协作关系其实很简单你可以直接在命令行敲mvn plugin:goal手动触发某个动作也可以把 Goal 绑定到某个 Phase 上让它在构建到那个阶段时自动执行。比如将某个 goal 绑定到 compile 阶段后任何人执行mvn compile或mvn package这个动作都会自动跑一遍。用一个流水线类比生命周期是传送带Phase 是传送带上的工位插件是工人在某个工位上干的活。你可以在传送带开动时手动叫某个工人干活命令行直接调用也可以让工人固定站在某个工位传送带经过时自动开工绑定 Phase。2.2 Mojo 注解声明一个 Goal 的核心配置写 Maven 插件最核心的类就是 Mojo 类每个 Mojo 对应插件里的一个 Goal。Mojo 类上必须标注Mojo注解常用的属性有这几个属性作用我的建议name定义 Goal 的名称命令行里插件前缀:name就是调它用动词或名词短名比如generateDocdefaultPhase默认绑定到哪个生命周期阶段比如编译后生成资源就填LifecyclePhase.GENERATE_RESOURCESrequiresDependencyResolution声明插件执行前需要解析到哪个粒度的依赖需要操作依赖树时用ResolutionScope.COMPILEthreadSafe声明该 Goal 是否线程安全建议显式声明true并行构建时避免被串行化关于 defaultPhase一个常见坑是开发者在命令行直接用mvn 插件:goal调试时发现目标没有执行因为直接调用 Goal 时不会经过生命周期的阶段调度defaultPhase 只在绑定到生命周期的场景下才起作用。手动调用时你就是老板让工人干哪项就直接点名干哪项。2.3 插件的坐标与插件前缀Maven 怎么找到你的插件Maven 找插件和找普通依赖类似也是通过 groupId、artifactId、version 这三个坐标。但命令行里直接写全坐标非常啰嗦比如mvn com.example:changelog-maven-plugin:1.0.0:hello所以 Maven 提供了插件前缀机制来简化调用如果插件的 artifactId 符合xxx-maven-plugin或maven-xxx-plugin的命名规范Maven 可以推导出前缀规则。artifactId 为changelog-maven-plugin那么前缀就是changelog命令行就可以简写成mvn changelog:hello这个前缀也可以用maven-plugin-plugin的goalPrefix配置强行指定。命名规范还是建议遵守因为很多团队约定会直接按前缀找插件命名奇怪后期维护成本就上来了。3. 第一个能跑起来的插件项目骨架与 Hello Mojo 完整实现原理讲再多也不如亲手跑通一个例子。我带你把整个流程走一遍从新建项目到mvn package后成功输出一行日志。3.1 环境准备JDK、Maven 与本地仓库做插件开发环境上首先要有 JDK8 以上都行我建议用 11兼容性更好和 Maven 3.6 以上的版本。如果你还没有配置过 Maven记住三个步骤下载 Maven 压缩包后解压配置MAVEN_HOME或M2_HOME环境变量再把MAVEN_HOME/bin加入PATH。命令行里执行mvn -v能打印出版本信息基本就算配置完成。国内环境强烈建议在settings.xml里配置阿里云私服镜像不然插件构建时下载依赖能等到怀疑人生。配置方式就是在settings.xml的mirrors节点加一个 mirror把mirrorOf设为centralurl指向阿里云的 maven 仓库地址。这一步虽然基础但真的很影响体验。3.2 创建插件项目的 pom.xml 骨架新建一个普通 Maven 工程groupId 我用com.exampleartifactId 命名为changelog-maven-pluginversion 用1.0.0-SNAPSHOT。关键点packaging 必须是maven-plugin这样 Maven 才知道要按插件方式构建并生成插件描述文件 plugin.xml。pom.xml 里的几个要素如下project modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdchangelog-maven-plugin/artifactId version1.0.0-SNAPSHOT/version packagingmaven-plugin/packaging properties maven.compiler.source8/maven.compiler.source maven.compiler.target8/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties dependencies dependency groupIdorg.apache.maven/groupId artifactIdmaven-plugin-api/artifactId version3.8.7/version scopeprovided/scope /dependency dependency groupIdorg.apache.maven/groupId artifactIdmaven-core/artifactId version3.8.7/version scopeprovided/scope /dependency dependency groupIdorg.apache.maven.plugin-tools/groupId artifactIdmaven-plugin-annotations/artifactId version3.9.6/version scopeprovided/scope /dependency /dependencies build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-plugin-plugin/artifactId version3.9.6/version configuration goalPrefixchangelog/goalPrefix /configuration /plugin /plugins /build /projectmaven-plugin-api提供 Mojo 接口和基类maven-core提供 MavenProject、MavenSession 这些运行时模型类maven-plugin-annotations提供Mojo、Parameter注解。这三个依赖都用provided作用域因为最终插件运行在 Maven 容器内这些类由 Maven 本身提供不需要打进插件 jar。3.3 编写第一个 Mojo输出项目基础信息在src/main/java/com/example下创建HelloMojo.java一个最简单却五脏俱全的插件 Goalpackage com.example; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; Mojo(name hello, defaultPhase LifecyclePhase.GENERATE_RESOURCES, threadSafe true) public class HelloMojo extends AbstractMojo { Parameter(defaultValue ${project}, readonly true) private org.apache.maven.project.MavenProject project; Override public void execute() throws MojoExecutionException, MojoFailureException { getLog().info(你好我是插件里的 Hello Goal); getLog().info(当前工程: project.getArtifactId() - project.getVersion()); } }这里有三个必须理解的点。第一所有 Mojo 都要继承AbstractMojo它帮你实现了日志、插件上下文访问这些基础能力第二execute()就是核心逻辑入口Maven 执行这个 Goal 时调用的就是它第三Parameter(defaultValue ${project})表示把当前 Maven 项目模型注入到project字段这样 Mojo 里就能拿到 artifactId、version、目录等上下文信息。编译并安装插件mvn clean installinstall 之后插件就进了本地仓库。现在去任何一个普通 Maven 工程里执行mvn com.example:changelog-maven-plugin:1.0.0-SNAPSHOT:hello或者用更短的前缀形式mvn changelog:hello如果看到你好我是插件里的 Hello Goal和工程名恭喜你的第一个 Maven 插件已经跑通了。3.4 绑定到生命周期阶段让它随 compile 自动执行命令行手动调用只是热身真正实用的是绑定到生命周期阶段。在目标工程的pom.xml里这样配置build plugins plugin groupIdcom.example/groupId artifactIdchangelog-maven-plugin/artifactId version1.0.0-SNAPSHOT/version executions execution idauto-run/id phasegenerate-resources/phase goals goalhello/goal /goals /execution /executions /plugin /plugins /build这里我显式声明了phasegenerate-resources/phase。注意即使 Mojo 注解里有 defaultPhase也可以在执行配置里覆盖默认阶段这个优先级是执行配置高于注解的。绑定完之后执行mvn compile或mvn generate-resources都会看到插件输出日志。到这里你就掌握了插件从开发到引用的完整闭环。接下来要让插件真正解决业务问题光输出日志是远远不够的。4. 让插件真正干活参数注入、项目上下文与文件输出真实场景里的插件很少只打一行日志几乎都要接收用户配置、处理文件、生成产物。这一章就是把插件从能跑推到能用的关键进阶。4.1 Parameter 参数注入从写死到可配置之前在HelloMojo里已经用过一次Parameter它注入 Maven 的运行时模型。实际上Parameter也能接收用户在pom.xml插件配置里写的参数以及命令行-D传入的系统属性。来看一个带参数的 Mojo。比如我需要一个生成文件清单的功能用户希望指定输出文件名同时提供一个开关控制是否显示完整信息Mojo(name generate-list, defaultPhase LifecyclePhase.GENERATE_RESOURCES, threadSafe true) public class GenerateListMojo extends AbstractMojo { Parameter(property changelog.outputName, defaultValue file-list.txt) private String outputName; Parameter(property changelog.showDetail, defaultValue true) private boolean showDetail; Override public void execute() throws MojoExecutionException, MojoFailureException { getLog().info(输出文件名: outputName); getLog().info(是否展示详情: showDetail); } }property属性定义了命令行-D的键用户在目标工程里执行mvn changelog:generate-list -Dchangelog.outputNamecustom.txt插件就能读到custom.txt而不是默认值。在目标工程的 pom 里也可以写死配置configuration outputNameapi-doc.md/outputName showDetailfalse/showDetail /configurationpom 里的configuration配置优先级高于注解默认值但低于命令行-D参数。这个优先级顺序一定要记住命令行-D参数 pom 里configuration配置 注解defaultValue。4.2 注入 MavenProject 与 MavenSession 读取项目上下文如果要读取模块坐标、依赖列表、构建目录就需要注入MavenProject。常见注入方式除了Parameter(defaultValue ${project})还有Parameter(defaultValue ${session})拿 MavenSession。我的经验是项目模型用MavenProject就够session 偶尔在读取用户属性、活动 profile 时用。读取项目依赖最直接的方式是project.getArtifacts()它会返回解析后的依赖 artifact 集合。所谓 artifact可以理解成带坐标的依赖产物。Parameter(defaultValue ${project}, readonly true) private MavenProject project; // 在 execute 里 SetArtifact artifacts project.getArtifacts(); for (Artifact artifact : artifacts) { getLog().info(artifact.getGroupId() : artifact.getArtifactId() : artifact.getVersion()); }注意一个容易混淆的地方project.getDependencies()拿到的只是当前模块 pom 里直接声明的依赖而project.getArtifacts()拿到的是 Maven 已经解析完毕的完整依赖树中实际参与构建的依赖。如果只想看显式声明的用前者想知道最终会用到哪些 jar用后者。4.3 在 target 目录生成文件输出规范与路径技巧插件作为构建工具产物通常应该放在target目录下这是 Maven 的约定用户执行mvn clean时能把这些产物一并清掉。获取输出目录的标准姿势如下Parameter(defaultValue ${project.build.directory}, readonly true) private File buildDirectory;拿到buildDirectory后就可以在 execute 里创建子目录并写入文件。比如生成文件清单Override public void execute() throws MojoExecutionException { try { File outputDir new File(buildDirectory, changelog); if (!outputDir.exists()) { outputDir.mkdirs(); } File outputFile new File(outputDir, outputName); try (PrintWriter writer new PrintWriter(outputFile, StandardCharsets.UTF_8)) { writer.println(# 模块: project.getArtifactId()); writer.println(# 版本: project.getVersion()); for (Artifact artifact : project.getArtifacts()) { writer.println(artifact.getGroupId() : artifact.getArtifactId() : artifact.getVersion()); } } getLog().info(文件已生成: outputFile.getAbsolutePath()); } catch (IOException e) { throw new MojoExecutionException(生成依赖清单失败, e); } }这里有几个小细节。第一文件写入编码一定要显式指定 UTF-8否则在 Windows 上会按平台默认编码输出中文内容全变乱码第二用PrintWriter时记得包在 try-with-resources 里避免异常时文件句柄泄漏第三目录创建前先判断exists()避免不必要的 IO 操作。4.4 日志规范与异常设计别让堆栈刷屏插件开发中日志输出绝对是用户体验的重要组成部分。用AbstractMojo提供的getLog()分info()、warn()、error()三个级别就够用。千万别干一件事在插件里直接System.out.println。原因很简单Maven 输出有格式控制getLog()能带前缀和着色裸用System.out输出既没有级别控制在并行构建时还会乱序。异常设计更值得重视。execute()方法声明里有两个异常类型很多人分不清MojoExecutionException表示插件在执行过程中遇到了意外情况比如读取文件失败、依赖解析失败。这是工具本身出错了。MojoFailureException表示业务校验不通过比如发现代码规范违反。这是工具的检查对象没通过。两者都会让构建失败但语义不一样。按业务场景去选校验失败用MojoFailureException运行环境异常用MojoExecutionException。这样看日志时才能第一眼判断出是插件 bug 还是业务违规。5. 调试和排错远程调试配置与四个高频翻车现场插件写多了必然会遇到 bug而插件调试和普通应用调试不一样因为它运行在 Maven 的进程中且默认情况下和你的 IDE 断点毫无关系。这里分享一套我长期使用的调试方案以及四个我至少帮同事排查过三轮的高频问题。5.1 本地远程调试mvnDebug 与 IDEA 断点插件的调试本质上是远程调试让 Maven 进程启动时开启 JVM 调试端口然后 IDEA 作为调试客户端连接上去。Maven 发行版自带一个mvnDebug脚本在 Maven 安装目录的bin下。在目标工程里执行mvnDebug changelog:generate-list脚本默认会开启 8000 端口并等待调试器连接。如果用的是自定义参数也可以通过MAVEN_OPTS手动指定MAVEN_OPTS-agentlib:jdwptransportdt_socket,servery,suspendy,address8000 mvn changelog:generate-list然后打开 IDEA进入Run - Edit Configurations - 左上角 - Remote JVM DebugHost 填localhostPort 填8000最关键的设置是在Use module classpath那里选择你的插件模块而不是目标工程模块。点 Debug 连接后Maven 进程会继续执行你的断点就能正常命中了。这个调试方案我实测下来非常稳唯一的心理预期是每次修改插件代码后都要重新mvn install一次再回到目标工程执行。本地迭代改代码时这个 install 步骤是无法跳过的。5.2 翻车现场一No plugin found for prefix changelog刚写完插件在目标工程里执行mvn changelog:hello结果报错No plugin found for prefix changelog这是最常见的入门翻车点原因有四个。最傻的一种插件压根没 install 到本地仓库。你只执行了mvn package插件 jar 还在 target 目录里本地仓库里没有对应坐标Maven 自然找不到。解决方式先在插件项目里执行mvn install。第二种artifactId 命名不符合前缀推导规则。前缀推导是去掉maven-前缀或-maven-plugin/-plugin后缀如果你的 artifactId 是changelog-my-plugin注意不是changelog-maven-plugin也不是my-changelog-plugin前缀推导就会得到奇怪的结果。规规矩矩命名能省掉大量这种麻烦。第三种自定义 groupId 不在 Maven 默认的 pluginGroups 里。Maven 默认只会去org.apache.maven.plugins和org.codehaus.mojo这两个 group 的仓库里解析插件前缀。自定义的com.example前缀悄悄话需要去settings.xml里加pluginGroupcom.example/pluginGroup才能用前缀形式。如果不想改全局配置就老老实实用完整坐标调用mvn com.example:changelog-maven-plugin:1.0.0-SNAPSHOT:hello第四种版本号写错。用了-SNAPSHOT版本时本地仓库里如果没有对应时间戳的 snapshot 构件也会解析失败。清理本地仓库对应目录后重新 install 一般能解决。5.3 翻车现场二Parameter 注入不生效值一直是默认值参数注入不生效是我排查过最多的问题。典型现象是用户在目标工程 pom 的configuration里写了outputNameapi-doc.md/outputName但插件里outputName还是默认值。第一个排查点是字段名和 XML 节点名是否一致。Maven 支持驼峰字段名映射到 XML 连字符节点名也就是outputName既能识别outputName也能识别output-name但outputName和output_name就完全不互通。统一用驼峰字段加连字符 XML 是最稳的组合。第二个排查点是Parameter的property值。如果-D参数键名写错了命令行参数不会生效defaultValue就会接管。比如配置了property changelog.outputName命令行必须是-Dchangelog.outputNamexxx少写一个changelog.前缀就不行。第三个排查点隐蔽得多Mojo 类里如果没有无参构造器或者字段是final的Plexus 容器就没办法完成实例化和注入。所以写插件类时不要定义有参构造器不要给Parameter字段加final修饰。5.4 翻车现场三插件里拿不到目标项目的依赖类这是一个概念性误区。有朋友想写一个插件直接检查目标项目里某个第三方类是否被正确使用于是打算在插件代码里import那个第三方类。结果编译期就 Failed因为插件项目自己的依赖里根本没有那个类。即使你把那个类作为依赖加进了插件 pom运行时也可能出现版本冲突。原因在于 Maven 加载插件使用的是独立的 classloader realm它和项目的 classpath 是隔离的。项目依赖的 jar在插件运行时默认不进入插件的类加载范围。插件能用到的类是插件自身 pom 里声明的依赖而不是目标项目 pom 里的依赖。要处理目标项目依赖里的资源有几个正路如果是解析字节码别依赖第三方类直接用 ASM 或 Javassist 读文件如果是资源扫描直接从project.getArtifacts()拿 jar 的路径再自行解析如果要动态调用目标项目的类可以用 URLClassLoader 加载项目的输出目录。这三种方式我都实践过最推荐第一种因为既没有 classloader 进出的烦恼也不容易出现版本冲突。5.5 翻车现场四绑定了 Phase 却不执行还有一类问题插件明明配置了phasecompile/phase执行mvn package时它却不跑。遇到这种问题按顺序排查三件事。第一看目标工程 pom 里插件配置是不是写在pluginManagement而不是plugins。pluginManagement只是声明性的配置不会真正激活插件执行只有plugins里的才会生效。第二看目标工程执行命令时有没有跳过阶段。比如你绑定到process-classes阶段但用户直接执行mvn compile而 compile 阶段排在process-classes前面自然触发不了。绑定阶段时要考虑用户可能的调用方式。第三检查是不是多个 execution 的 id 重复或者 execution 里configuration的skip被设成了 true。skip 这种开关我是强烈建议统一用属性来控制比如-Dchangelog.skiptrue方便 CI 里临时跳过。6. 从 Demo 到工程化测试、线程安全与插件命名那些事插件能用和插件好用是两回事。个人项目里自己能用就行团队里别人也要用的话就得按工程化的标准去打磨了。这一章聊聊我在插件质量、并发和文档方面积累的建议。6.1 给插件写自动化测试从手动 Process 到 Invoker插件本身也是一个 Maven 工程它的逻辑可以拆分成普通 Java 类用 JUnit 测试这没毛病。但插件在 Maven 容器里能否正确获取参数、执行内容、产生预期文件这种集成层面的事普通单测测不出来。我常用的方案是maven-invoker-plugin它是 Maven 官方提供的用于跑集成测试的插件可以让你在测试时新建一组临时工程每个临时工程都配置好你的插件坐标然后调用mvn执行指定命令最后断言输出文件的内容。配置思路很简单插件项目里加一个src/it目录下面放若干个测试工程每个工程对应一种使用场景比如绑定到某个 Phase、多个参数组合、跳过开关。然后maven-invoker-plugin会在 verify 阶段逐个执行这些场景任何场景构建失败都直接导致插件项目构建失败。这套方案的好处是测试环境和真实用户环境几乎一致很多本地调试正常但用户环境就是不工作的问题能在 CI 里提前暴露。6.2 threadSafe 与并行构建从只管跑通到关注并发Maven 3 开始支持并行构建mvn -T 1C install会让多个模块同时构建。并行构建下如果一个插件没有声明线程安全Maven 会把该插件的执行强制串行化。串行化不一定是坏事但会拖慢整体构建如果插件声明threadSafe true但内部有共享的静态全局状态那就可能引发真正的并发 bug。我的实践原则是所有Mojo都显式声明threadSafe true同时做到两点。第一Mojo 里不要使用静态可变变量所有状态都放在实例字段里——因为同一个插件的多个 execution 可能是由不同 Mojo 实例执行的但静态变量是跨实例共享的。第二输出文件路径要包含模块名或基于项目目录生成不同模块并行执行时不要同名文件互踩。6.3 插件命名、版本管理与插件文档让别人能接手如果你写的插件要交给团队其他同事用命名和版本规范一定要提前想好。插件 artifactId 用功能描述-maven-plugin这个模式最直观比如changelog-maven-plugin、code-check-maven-plugin。groupId 用公司内部统一前缀比如com.company.tools。版本遵循语义化版本新增功能提 minor有破坏性变更提 major修复 bug 只提 patch。插件是构建工具直接影响所有使用它的项目破坏性变更如果不升大版本很容易被打包的同事一不留神引入构建失败。文档这件事被很多人忽视。maven-plugin-plugin有一个helpgoal 和 descriptor 生成机制只要你在Mojo和Parameter上写清楚中文注释mvn help:describe -Dpluginchangelog -Dgoalhello -Ddetail就能生成参数说明文档。别小看这个自动生成的帮助信息同事遇到问题第一反应通常是敲mvn changelog:help文档写得好就是最好的技术支持。6.4 多 Goal 插件与代码结构别把业务逻辑堆在 Mojo 里插件逐渐变复杂后一个插件里有三五个 Goal 很常见。比如文件生成插件可以拆generate-list、check-file-content、clean-output三个 Goal每个 Goal 都是一个 Mojo 类。这时头脑一热把所有逻辑塞在 Mojo 里后期维护就是灾难。我踩过这个坑之后的教训是Mojo 只负责接收参数、调用业务逻辑、处理 Maven 相关的异常真正的解析、生成、校验逻辑抽成普通 Java 类放在util或core包下用 JUnit 单测。这样有几个好处核心逻辑跟 Maven 解耦可以快速做单元测试参数注入相关的坑不会扩散到业务代码里以后想把这套逻辑复用到 Gradle 插件或其他工具上搬起来也方便。从第一个 Hello Mojo 到现在能自动生成文档、能配合 CI 做代码校验的插件我前后迭代了小半年。整体体会是写 Maven 插件最大的门槛反而不是 Java 或者 API而是理解 Maven 把构建过程抽象成了什么。一旦想通了生命周期、Phase、Goal 和插件参数这套模型剩下的只是往里面填你自己的业务逻辑而已。如果你也正被构建过程中的重复劳动困扰花一两天把一个最小的插件跑通再慢慢往里面加逻辑这个投入相当值得。
返回列表