我的项目中需要在自定义maven插件中调用javadoc获取java源码的注释,就需要为了javadoc能正常解析源码,还需要源码所在项目的依赖库列表(java 9以上版本的javadoc这是必须的)作为-classpath
.
方案一:dependency:build-classpath
如果在项目安装(install)阶段(phase),这个参数通过执行org.apache.maven.plugins:maven-dependency-plugin:build-classpath插件的build-classpath目标就可以实现
如下:
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-dependency-plugin</artifactId><version>2.9</version><executions><execution><phase>generate-sources</phase><goals><goal>build-classpath</goal></goals><configuration><!-- 结果输出到指定的属性名 --><outputProperty>maven.compile.classpath</outputProperty></configuration></execution></executions></plugin>
将maven.compile.classpath
这个属性的值作为参数传递给自定义插件就可以了.示例如下:
<plugin><!-- 自定义插件 --><groupId>com.gitee.l0km</groupId><artifactId>codegen-decorator-maven-plugin</artifactId><version>${codegen.version}</version><executions> <execution><goals><goal>generate</goal></goals></execution></executions><configuration><!-- 引用maven-dependency-plugin插件生成的maven.compile.classpath属性值 --><classPath>${maven.compile.classpath};${project.build.outputDirectory}</classPath><!-- 省略其他配置 --></configuration></plugin>
方案二
但是方案一只能在maven的 install, package阶段生效,
如果我希望命令执行自定义插件,这时不是maven生命周期的任何一个阶段,phase为NONE,
所以不能通过执行dependency:build-classpath
将maven.compile.classpath
的值传递给自定义插件,所以方案一就失效了.
所以只能另想办法,
那么能不能在自定义插件中调用Maven API获取当前项目的依赖库列表生成classpath呢?
我问了AI,果然是可以的,
并且给出了代码,还挺简单:
package com.example;import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;import java.util.List;@Mojo(name = "list-dependencies", defaultPhase = LifecyclePhase.INSTALL)
public class ListDependenciesMojo extends AbstractMojo {@Parameter(defaultValue = "${project}", readonly = true, required = true)private MavenProject project;@Overridepublic void execute() throws MojoExecutionException {/** 获取项目所有依赖库列表,提取文件路径生成classpath */String classpath = project.getArtifacts().stream().map(artifact -> artifact.getFile().getAbsolutePath()).filter(file -> file != null).collect(Collectors.joining(File.pathSeparator));/** 获取输出目录 */getLog().info("classpath:"+classpath);}
}
当然还需要org.apache.maven:maven-core
依赖的支持
<dependencies><dependency><groupId>org.apache.maven</groupId><artifactId>maven-core</artifactId><version>3.6.3</version></dependency></dependencies>
上面这个方案基本的逻辑就是通过插件注入的org.apache.maven.project.MavenProject
实例的getArtifacts()
方法获取所有依赖库列表,生成classpath.
但是实际执行时发现getArtifacts()
方法返回的永远是空.
我这才明白上面的示例代码中@Mojo
注解定义的defaultPhase
(默认阶段)为INSTALL
,也就是说在非INSTALL,PACKAGE
阶段getArtifacts()
方法是不返回有效数据的.
而我的自定义插件要求运行时不依赖maven的生命周期任何阶段.也就是defaultPhase=LifecyclePhase.NONE
.
在google上一通找,下面这stackoverflow上的贴子给了我灵感
《how-to-get-access-to-mavens-dependency-hierarchy-within-a-plugin》
其中一个作者的回复提示了重要信息,正是我所需要的,如下图红线圈出的部分.
上面这个作者在@Mojo
注解中定义了requiresDependencyCollection,requiresDependencyResolution
才是解决问题的关键.
于是果断修改代码,如下在@Mojo
注解中增加了requiresDependencyCollection,requiresDependencyResolution
定义,getArtifacts()
方法就可以正常返回依赖库数据了.
package com.example;import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;import java.util.List;@Mojo(name = "list-dependencies", requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class ListDependenciesMojo extends AbstractMojo {@Parameter(defaultValue = "${project}", readonly = true, required = true)private MavenProject project;@Overridepublic void execute() throws MojoExecutionException {/** 获取项目所有依赖库列表,提取文件路径生成classpath */String classpath = project.getArtifacts().stream().map(artifact -> artifact.getFile().getAbsolutePath()).filter(file -> file != null).collect(Collectors.joining(File.pathSeparator));/** 获取输出目录 */getLog().info("classpath:"+classpath);}
}
requiresDependencyCollection,requiresDependencyResolution
requiresDependencyCollection,requiresDependencyResolution
的含义是什么?
很幸运,关于requiresDependencyCollection,requiresDependencyResolution
定义,我找到了比较完整和权威的解释,我就不多写了,直接抄中文翻译.
源自sonatype.com网站的文章《What’s in Maven 3.0 for Plugin Authors?》
访问项目依赖项(requiresDependencyResolution
)
插件获取项目的整个传递依赖项集的最简单方法是 mojo 注解@requiresDependencyResolution
。此注解配置了以下值之一: compile, runtime 或 test,它们表示插件感兴趣的依赖项范围。已被证明麻烦的是,运行时 Classpath 不是编译 Classpath 的超集,即提供的 scope 和 system 的依赖项不包含在运行时 Classpath 中。
现在,插件作者应该做什么来检查编译和运行时类路径的并集呢?到目前为止,答案是请求测试 Classpath,或者使用适当的 scope 过滤器以编程方式进行依赖项解析。出于这样或那样的原因,这两种方法都没有真正的吸引力。因此,我们最终在 Maven 3.0 中添加了对新注释值 compile+runtime 的支持,以确保解决 compile、runtime、provided 和 system 等范围的项目依赖关系。
需要依赖项收集(requiresDependencyCollection
)
当我们进行依赖项解析时,Maven 3.0 还引入了新的注释@requiresDependencyCollection
。诚然,这个新注释的名称与现有的 @requiresDependencyResolution
注释仅略有不同,但在效果方面存在相当重要的差异。
依赖项解析包括两个阶段。第一阶段是确定所有传递依赖项的坐标,第二阶段是获取这些工件的文件,这可能涉及下载它们。如果一个人只对第一阶段感兴趣,我们就来谈谈依赖集合。更详细地说,如果插件目标声明@requiresDependencyCollection
编译,它可以通过 MavenProject.getArtifacts()
检查项目的所有传递编译时依赖项。但与 @requiresDependencyResolution
不同的是,返回的工件实例可能是未解析的,即没有与之关联的文件。
有未解决的工件似乎很奇怪,但让我试着勾勒出激发这一点的用例。虽然未解析的工件不能用于创建 Classpath,但它们仍然允许插件检查传递依赖项集、查找不需要的工件、检查版本等。如果这让您想起了 Maven 发布插件或 Maven Enforcer 插件,那就太好了。这些插件和其他插件通常在构建的非常早期的生命周期阶段被调用,特别是在打包任何工件甚至编译任何类之前。如果这些插件在多模块构建期间尝试解决具有项目间依赖关系的依赖关系,则它们不会成功,尚未构建的内容也无法解析。
在 Maven 2.x 中,如果所有缺失的工件都在 reactor 中,则有一个 hack 可以容忍解析错误,但它仍然会给您的构建留下一个丑陋的警告,并且有问题的插件没有满足需求。@requiresDependencyCollection
是这种情况的答案,将有助于最终解决前面提到的插件中一些长期存在的问题。因此,如果您的插件不需要实际文件,而只需要项目依赖项的坐标,那么在将 Maven 3.0 作为先决条件时,请考虑使用新注解。
总结
方案二的好处显而易见,因为插件内部就能搞定项目的依赖库,如本例中,自定义插件不再需要通过定义一个参数(classpath)从外部传递项目依赖,也不再需要在pom.xml中定义maven-dependency-plugin插件获取classpath,简化了自定义插件的使用,而且场景适应性更好.
参考资料
《how-to-get-access-to-mavens-dependency-hierarchy-within-a-plugin》
《What’s in Maven 3.0 for Plugin Authors?》