Maven 4 API 插件描述符模型(Plugin Descriptor Model)完全指南:从 plugin.mdo 到不可变 Java 模型
【免费下载链接】mavenApache Maven core项目地址: https://gitcode.com/GitHub_Trending/ma/maven
导读
本篇指南聚焦于 Apache Maven 4 新增的插件描述符不可变模型(Immutable Plugin Descriptor Model)——它是 Maven 4 API 中定义插件元数据、配置与执行参数的核心数据模型,最终以META-INF/maven/plugin.xml的形式驻留在每个插件 JAR 中。文章以 api/maven-api-plugin/src/site/markdown/index.md 文档为骨架,结合仓库中 plugin.mdo 与 lifecycle.mdo 的完整字段定义、代码生成流程与测试用例,带你掌握:描述符模型如何由 Modello 从.mdo声明式生成、PluginDescriptor/MojoDescriptor/Parameter等核心类承载哪些语义,以及如何用生成的Builder以不可变方式构造插件描述符。
一、插件描述符模型:Maven 4 插件元数据的新家
1.1 文档定位与包结构
在 Maven 4 的模块化 API 架构中,插件描述符模型位于org.apache.maven.api.plugin.descriptor包,其包级注释明确说明:
Provides classes for Maven plugin descriptors that define plugin metadata, configuration, and execution parameters. These descriptors are typically stored in plugin.xml files within the META-INF/maven directory of plugin JARs.(见 package-info.java)
该包由 api/maven-api-plugin 模块提供,模块描述为 "Maven 4 API - Immutable Plugin model.",其依赖仅包括maven-api-annotations与maven-api-xml,不依赖任何运行时容器——这是一个纯粹的、可独立复用的数据模型 API。
1.2 描述符的物理载体:plugin.xml
plugin.mdo的模型级注释给出了描述符的物理位置与语义:
Maven 4 Plugin descriptor, stored in
META-INF/maven/plugin.xmlin a plugin's jar artifact. This descriptor is generally using the information contained in the annotations of the plugin api.
也就是说,Maven 4 中一个插件 JAR 内的META-INF/maven/plugin.xml就是本模型的实例化产物,其内容通常由插件 API 上的注解(如@Mojo、@Parameter等)推导而来。这一约定在实现层同样被引用:解析插件元数据时,PluginsMetadataGenerator.java 中常量PLUGIN_DESCRIPTOR_LOCATION = "META-INF/maven/plugin.xml"直接确认了读取位置。
二、模型从何而来:Modello 声明式生成
与手写 Java 类不同,Maven 4 的插件描述符模型采用Modello(MODELLO 2.0)声明式建模 + 代码生成的方式。模型的"唯一事实来源"是两个.mdo文件:
- plugin.mdo(678 行):定义插件描述符本体(PluginDescriptor、MojoDescriptor、Parameter 等类);
- lifecycle.mdo(162 行):定义自定义生命周期映射模型(LifecycleConfiguration、Lifecycle、Phase、Execution)。
生成行为在 pom.xml 中通过modello-maven-plugin的两个 execution 配置完成:
<plugin> <groupId>org.codehaus.modello</groupId> <artifactId>modello-maven-plugin</artifactId> <executions> <execution> <id>modello-plugin</id> <goals><goal>velocity</goal><goal>xdoc</goal><goal>xsd</goal></goals> <phase>generate-sources</phase> <configuration> <velocityBasedir>${project.basedir}/../../src/mdo</velocityBasedir> <version>2.0.0</version> <models> <model>src/main/mdo/plugin.mdo</model> </models> <templates> <template>model.vm</template> </templates> <params> <param>packageModelV4=org.apache.maven.api.plugin.descriptor</param> </params> </configuration> </execution> <!-- 第二个 execution(id=modello-lifecycle)以同样方式处理 lifecycle.mdo, 目标包为 org.apache.maven.api.plugin.descriptor.lifecycle --> </executions> </plugin>要点解读:
- velocity 模板:
model.vm是共享的代码生成模板,位于仓库 src/mdo/model.vm,与ImmutableCollections.java、InputLocation.java等手写辅助类共同构成生成器的骨架; - 两个模型两个包:
plugin.mdo生成到org.apache.maven.api.plugin.descriptor,lifecycle.mdo生成到其子包org.apache.maven.api.plugin.descriptor.lifecycle; - 产物:除 Java 源码外,还生成
xdoc(站点文档)与xsd(XML Schema)。plugin.mdo头部声明的 schema 位置为https://maven.apache.org/xsd/plugin-${version}.xsd,命名空间为http://maven.apache.org/PLUGIN/${version}。
从源码结构看,生成出来的 Java 类本身并不提交在仓库中(src/main/java下只有package-info.java),而是在generate-sources阶段由 Modello 生成——这正是"模型即源码"的声明式开发方式。
三、PluginDescriptor:根元素模型
PluginDescriptor是plugin.xml的根元素类(rootElement),在.mdo中对应<class rootElement="true" xml.tagName="plugin">。它描述的是插件整体的元信息。
3.1 字段清单与语义
| 字段 | 版本 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
name | 1.0.0+ | String | 否 | — | 插件名称 |
description | 1.0.0+ | String | 否 | — | 插件描述 |
groupId | 1.0.0+ | String | 是 | — | 插件 groupId |
artifactId | 1.0.0+ | String | 否 | — | 插件 artifactId |
version | 1.0.0+ | String | 是 | — | 插件版本 |
goalPrefix | 1.0.0+ | String | 否 | — | 命令行前缀(如mvn compiler:compile中的compiler) |
isolatedRealm | 1.0.0+ | boolean | 否 | false | 是否使用隔离的 class realm |
inheritedByDefault | 1.0.0+ | boolean | 否 | true | 插件配置默认是否被继承 |
requiredJavaVersion | 1.1.0+ | String | 否 | — | 支持的 Java 版本范围(见下文) |
requiredMavenVersion | 1.1.0+ | String | 否 | — | 支持的 Maven 版本范围,优先级高于 POM 的 prerequisites |
mojos | 1.0.0+ | MojoDescriptor[] | 否 | — | 插件提供的每个 Mojo 的描述 |
dependencies | 1.0.0/1.1.0 | Dependency[] | 否 | — | 插件运行所需的依赖集合 |
其中两个新增字段值得特别说明:
版本范围语法(requiredJavaVersion/requiredMavenVersion,自 Maven 4.0.0-alpha-3 起):既支持数学区间语法如[2.0.10,2.1.0),[3.0,),也支持单版本短形式2.2.1(等价于[2.2.1,),即"最低版本")。requiredMavenVersion的描述特别强调:此值优先于 POM 中的 Maven prerequisites,是 Maven 4 声明插件运行环境约束的首选方式。
便捷方法:模型中还以 codeSegment 方式为生成的类注入了两个派生方法(版本 2.0.0+):
public String getPluginLookupKey() { return groupId + ":" + artifactId; } public String getId() { return groupId + ":" + artifactId + ":" + version; }getPluginLookupKey()用于插件查找/解析键,getId()则给出完整的插件坐标标识。
四、MojoDescriptor:单个目标的完整语义
MojoDescriptor(xdoc anchormojo)描述插件提供的每一个 Mojo(目标)。理解它,就理解了 Maven 插件执行的绝大部分行为约定。其字段可分为若干组。
4.1 身份与执行定位
| 字段 | 版本 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
goal | 1.0.0+ | String | 是 | — | 目标名,用户从命令行或 POM 中引用 |
implementation | 1.0.0+ | String | 否 | — | Mojo 的全限定类名(非 Java Mojo 可为脚本路径) |
language | 1.0.0+ | String | 否 | java | 实现语言(java、beanshell 等) |
phase | 1.0.0+ | String | 否 | — | 默认绑定的生命周期阶段 |
executePhase/executeGoal/executeLifecycle | 1.0.0+ | String | 否 | — | 执行前需要触发的阶段/目标/生命周期 |
instantiationStrategy | 1.0.0/1.1.0 | String | 否 | per-lookup | 实例化策略 |
executionStrategy | 1.0.0/1.1.0 | String | 否 | once-per-session | 执行策略:once-per-session、always |
关于phase,.mdo中有一段重要的澄清性说明(引用自模型注释):它并不会让插件声明一加入 POM 就"神奇地"自动运行,其作用仅仅是允许用户在<execution>中省略<phase>元素——绑定的执行仍然必须通过<executions>显式声明。
4.2 依赖解析与收集要求
这是 Maven 插件行为契约中最关键的一组字段,值得注意的是它在 2.0.0 版本经历了字段改名:
| 1.0.0/1.1.0 旧字段 | 2.0.0+ 新字段 | 说明 |
|---|---|---|
requiresDependencyResolution | dependencyResolution | 要求指定 classpath 的依赖在执行前完成解析,取值compile、runtime、test、compile+runtime、runtime+system |
requiresDependencyCollection | dependencyCollection | 只要求收集依赖信息、不解析文件,适用于早期生命周期阶段(此时部分项目尚未构建,完整解析可能失败) |
requiresDirectInvocation | directInvocationOnly | 只能被直接调用 |
requiresProject | projectRequired | 必须在项目中运行(默认true) |
requiresOnline | onlineRequired | 需要在线模式 |
threadSafe(1.0.0/1.1.0) | —(2.0.0 移除) | 线程安全标记,标记线程安全可避免并行构建警告 |
dependencyCollection的注释还特别提醒:不解析文件意味着项目关联的 artifact 可以缺少文件,这类注解适合只想分析传递依赖集合的 Mojo——典型场景就是早期生命周期阶段。
4.3 其他行为标记
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
aggregator | boolean | false | 多模块聚合运行 |
inheritedByDefault | boolean | true | Mojo 是否被继承 |
v4Api(1.1.0) | boolean | false | 标记使用 Maven 4 API,隐式与早期 Maven 不兼容,仅在 Maven 4 中被评估 |
since | String | — | 加入 API 的版本(类似 Javadoc since) |
deprecated | String | — | 弃用原因描述,用户使用时会触发警告 |
configurator | String | — | 注入参数时使用的 configurator 类型,通常由实现语言推导,可指定自定义 ComponentConfigurator |
composer(1.0.0/1.1.0) | String | — | 组合器(已由后续版本演进) |
4.4 派生字段与新版关联
自 2.0.0 起,模型新增了两个由 Modello 的xml.format表达式自动计算的字段:
<field xml.format="((PluginDescriptor.Builder) context.peekLast()).build().getId() + ":" + mojoDescriptor.build().getGoal()"> <name>id</name> <!-- 形如 groupId:artifactId:version:goal --> </field> <field xml.format="((PluginDescriptor.Builder) context.peekLast()).build().getGoalPrefix() + ":" + mojoDescriptor.build().getGoal()"> <name>fullGoalName</name> <!-- 形如 prefix:goal --> </field>同时 2.0.0+ 还引入两个新的关联集合:
resolutions(Resolution[]):依赖收集/解析注入声明,Resolution包含field(注入字段名)、pathScope(扁平化依赖的路径作用域)、requestType(collect、flatten、resolve之一);afterLinks(AfterLink[]):来自 Mojo 类上@After注解的生命周期排序约束(@since 4.0.0)。
AfterLink是 Maven 4 精细化控制跨项目执行顺序的新机制,三个字段:
| 字段 | 必填 | 说明 |
|---|---|---|
phase | 是 | 本 Mojo 应在其后执行的目标阶段名 |
type | 是 | 指针类型:PROJECT(同项目阶段排序)、DEPENDENCIES(跨项目依赖排序)、CHILDREN(父子模块排序) |
scope | 否 | 依赖作用域,仅在type=DEPENDENCIES时有意义(如compile、runtime、test) |
五、Parameter 与 Requirement:配置注入契约
5.1 Parameter:Mojo 参数描述
Parameter描述单个可配置参数,字段如下:
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
name | String | 是 | — | 参数名,用于 POM 配置与默认值引用 |
alias | String | 否 | — | 配置别名,当 Mojo 字段名对用户不友好时提供更友好的配置名 |
type | String | 是 | — | 参数 Java 类型,用于校验注入表达式结果 |
required | boolean | 否 | — | 是否必填;注入前用于校验配置,避免 Mojo 在半成品状态执行 |
editable | boolean | 否 | true | 是否允许用户直接配置;设为false可强制用户使用通用 POM 元素(如<finalName>)而非插件配置,也可防止 List 类型参数被注入成字符串列表 |
description | String | 否 | — | 参数用途说明 |
since | String | 否 | — | 加入版本 |
deprecated | String | 否 | — | 弃用说明,配置时触发警告 |
expression(2.0.0+) | String | 否 | — | 参数表达式,允许用户用 user property / system property / project property 覆盖默认值 |
defaultValue(2.0.0+) | String | 否 | — | 默认值,作为在注入或运行时求值的表达式 |
editable=false的典型场景在模型注释中给出了两个具体例子:强制用户修改<build><finalName/>而不是在插件配置里直接指定 finalName;以及确保期望元素类型为 Artifact 的 List 参数不会被注入一串 String。
5.2 Requirement:已废弃的组件注入
Requirement(1.0.0/1.1.0)描述 Plexus 风格的组件需求(对应旧@Component注解),字段包括必填的role(Plexus 组件角色)、role-hint(role 提示)、field-name(被注入的字段名)。模型注释明确将其标记为Deprecated,并建议改用 JSR 330 注解注入组件而无需此描述符。MojoDescriptor.requirements字段同样带有 Deprecated 说明。这是 Maven 4 推动标准 DI 的明确信号。
5.3 Dependency:插件自身运行依赖
Dependency描述插件运行所需依赖(1.0.0/1.1.0),字段:groupId(必填)、artifactId(必填)、version、type(默认jar)。其用途是让插件独立于其 POM 声明运行所需类库。
六、Lifecycle 模型:自定义生命周期映射
除插件描述符外,本模块还生成自定义生命周期映射模型(org.apache.maven.api.plugin.descriptor.lifecycle包),物理载体为插件 JAR 内的META-INF/maven/lifecycle.xml(见 lifecycle.mdo 模型注释)。层级结构为:
LifecycleConfiguration (根元素 <lifecycles>) └── Lifecycle (id + phases) └── Phase (id + executionPoint + priority + executions + configuration) └── Execution (configuration + goals)Phase的两个 2.0.0+ 新属性值得关注:
executionPoint:若指定(如before、after),将本阶段标识为动态阶段,用于装饰指定的阶段;priority(int,默认0):阶段内执行次序的优先级。
Phase内嵌的 codeSegment 给出了**有效 ID(effectiveId)**的计算逻辑,这正是动态阶段在生命周期执行时被识别的关键:
public String getEffectiveId() { if (executionPoint == null) { if (priority == 0) { return id; } return id + '[' + priority + ']'; } if (priority == 0) { return executionPoint + ':' + id; } return executionPoint + ':' + id + '[' + priority + ']'; }即有效 ID 形如generate-sources、integration-test[1000]或after:integration-test[1000],把"装饰点 + 阶段 + 优先级"编码进一个可排序的字符串。
七、不可变性与 Builder 实践
7.1 生成类的不可变构造方式
原文档明确指出:模型生成物中包括"带Builder内部类的 Java 源码,用于创建不可变实例"。生成的不可变模型类是final风格、字段在构造时固定,必须通过内部Builder完成实例构建。
仓库中的测试 ExtendedPluginDescriptorTest.java 同时验证了两件事:模型类可被继承扩展,以及Builder 链式 API 的使用顺序约束:
static class ExtendedPluginDescriptor extends PluginDescriptor { private final String additionalField; ExtendedPluginDescriptor(Builder builder) { super(builder); this.additionalField = builder.additionalField; } static class Builder extends PluginDescriptor.Builder { protected String additionalField; Builder() { super(false); } public Builder additionalField(String additionalField) { this.additionalField = additionalField; return this; } @Override public ExtendedPluginDescriptor build() { return new ExtendedPluginDescriptor(this); } } } @Test void testExtendedPluginDescriptor() { ExtendedPluginDescriptor.Builder builder = new ExtendedPluginDescriptor.Builder(); // 必须先调用子类 Builder 的方法,否则流式 API 无法工作 builder.additionalField("additional") .groupId("org.apache.maven") .artifactId("maven-plugin-api") .version("1.0.0"); ExtendedPluginDescriptor descriptor = builder.build(); assertEquals("additional", descriptor.getAdditionalField()); assertEquals("org.apache.maven", descriptor.getGroupId()); }这个测试蕴含两个重要的实践结论:
- 扩展模式:要扩展生成的模型类,需要同时继承模型类和它的
Builder(调用super(false)),并重写build()返回子类型;子类字段必须在调用父类链式方法之前设置,否则流式 API 会因类型转换而失效(测试注释明确指出了这一点); - 不可变语义:实例创建后字段不可再变,
groupId/artifactId/version直接通过 Builder 方法注入,这正是 Maven 4 描述符模型与旧org.apache.maven.plugin.descriptor.PluginDescriptor(可变 POJO)在编程模型上的根本差异。
7.2 与 Maven 3 时代模型的差异(从源码结构看)
- 旧模型(
compat/maven-plugin-api等兼容模块)是可变、可继承的传统 POJO;新模型强调不可变与 Builder 构造,属于api/maven-api-plugin的新一代 API; - 新模型采用 Modello 声明式生成 +
maven-api-annotations注解协同(模块依赖见 pom.xml),.mdo文件是唯一事实来源; plugin.mdo中字段的1.0.0 / 1.1.0 / 2.0.0版本标注反映了模型演进历史:如requiresDependencyResolution→dependencyResolution的改名、v4Api的引入、afterLinks/resolutions的加入,均为 Maven 4 特性(@since 4.0.0、@since Maven 4.0.0-alpha-3)。
八、结语:一份模型,三层价值
围绕 api/maven-api-plugin/src/site/markdown/index.md 所指向的 Plugin Descriptor Model,本仓库给出了完整的可研读路径:
- 声明层:plugin.mdo 与 lifecycle.mdo 定义了全部类、字段、默认值与派生逻辑;
- 生成层:pom.xml 中的
modello-maven-plugin配置驱动 model.vm 模板,在generate-sources阶段产出不可变 Java 类、XSD 与文档; - 消费层:生成的
plugin.xml/lifecycle.xml由实现模块读取(如 PluginsMetadataGenerator.java 中的META-INF/maven/plugin.xml定位常量),测试 ExtendedPluginDescriptorTest.java 则示范了面向扩展的 Builder 使用范式。
无论你是 Maven 插件开发者、希望理解 Maven 4 内部插件元数据流转的贡献者,还是想复用它来构建自有描述符体系的架构师,都可以从这三个层次切入,把"模型即文档"的声明式设计思路应用到自己的项目中去。
【免费下载链接】mavenApache Maven core项目地址: https://gitcode.com/GitHub_Trending/ma/maven
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考