在 Maven 插件中调用 MavenCli 失败
Invocation of MavenCli fails within a Maven plugin
我创建了一个小工具来包装 MavenCli
,它使用快速启动原型生成一个新的 Maven 项目。
当执行 Util
作为单元测试时,它工作得很好(只是生成一个空的 Maven 项目)。
现在我想将这个小包装器集成到 Maven 插件中。但是当我执行 mojo(在第三个 Maven 项目中)时,MavenCli
的调用失败并出现异常:
[ERROR] Error executing Maven.
[ERROR] java.util.NoSuchElementException
role: org.apache.maven.eventspy.internal.EventSpyDispatcher
roleHint:
[ERROR] Caused by: null
实用程序如下所示:
public void createProject() {
final MavenCli cli = new MavenCli();
System.setProperty("maven.multiModuleProjectDirectory", "/usr/share/maven");
cli.doMain(new String[] { "archetype:generate", "-DgroupId=com.my.company",
"-DartifactId=hello-world", "-DarchetypeArtifactId=maven-archetype-quickstart",
"-DinteractiveMode=false" }, "/tmp", System.out, System.out);
}
util的相关依赖:
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
<version>3.3.9</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>3.3.9</version>
</dependency>
mojo 代码如下所示:
@Mojo(name = "custommojo", requiresProject = false)
public class CustomMojo extends AbstractMojo {
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
Util.createProject();
}
}
mojo 的 POM 仅包含对相关 Maven 工件(插件-api、插件注释等)和实用程序的依赖项。
我提到的第三个项目是一个空的 "maven-quickstart" 项目,它依赖于 mojo 项目和 mojo 在编译阶段执行的配置。
我不知道为什么它在单元测试的上下文中有效,但在 mojo 的上下文中却无效。
有人可以帮忙吗?
这是一个 class 加载问题。
MavenCli
将尝试从当前线程的上下文 classloader 加载 classes。在 Maven 插件内部,有 a special, restricted, classloader,它可以访问:
- 它自己的 classes;
- 在其依赖块中使用的 classes;
- 导出 classes 作为项目可能构建扩展的一部分;
- 从 Maven 核心和核心扩展导出 classes;
- 并且有 bootstrap classloader 作为父级。
但是,具体的 class org.apache.maven.eventspy.internal.EventSpyDispatcher
是 Maven 核心的一部分,但是 it is not part of the exported APIs(包 org.apache.maven.eventspy
没有列为 exportedPackage
) .所以插件无法加载 class。这也是它在您的测试中起作用的原因:您不在插件内部,因此 classloader 不同并且可以访问 class.
您甚至无法为插件显式添加对 maven-core
的依赖:它将被忽略,因为它应该已经提供了。
这里有2个解决方案:
- 不要在插件中使用 Maven Embedder API,而是 Invoker API。两者之间的区别在于 Invoker API 将在干净的环境中启动 Maven,与当前环境完全不同。由于它会重新开始一切,因此您不会遇到任何 class 加载问题。
- 使用 mojo-executor 库,它提供了一种从 Maven 插件中调用其他 Mojo 的简单方法。您可以在此处使用它来调用
archetype:generate
Mojo。
这在自定义 Maven 插件(使用 Maven 3.5.0)中对我有用:
ClassRealm classRealm = (ClassRealm) Thread.currentThread().getContextClassLoader();
MavenCli cli = new MavenCli(classRealm.getWorld());
cli.doMain( ... );
plexus Launcher sets the context class loader 到它的 ClassRealm
,它可以访问 "global" ClassWorld
。
不确定该解决方案有多稳定,但目前看来还不错。
二手进口:
import org.codehaus.plexus.classworlds.ClassWorld;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.apache.maven.cli.MavenCli;
@亚历山大:
你的回答让我朝着正确的方向前进,我在随后尝试 运行 doMain maven 命令时遇到了上述错误(一个命令会成功)。
但是你的代码给了我一个 ClassCast 异常:
Exception in thread "main" java.lang.ClassCastException: sun.misc.Launcher$AppClassLoader cannot be cast to org.codehaus.plexus.classworlds.realm.ClassRealm
at com.misys.tools.integration.codegen.cli.cmd.InstallApi.mavenInstallMchApi(InstallApi.java:58)
at com.misys.tools.integration.codegen.cli.cmd.InstallApi.run(InstallApi.java:50)
at com.misys.tools.integration.codegen.cli.OpenApiGen.main(OpenApiGen.java:26)
我设法将代码重写为:
MavenCli cli = new MavenCli(new ClassWorld("maven",Thread.currentThread().getContextClassLoader()));
然后嵌入式 maven 的 2 个后续 doMain 调用成功!
我创建了一个小工具来包装 MavenCli
,它使用快速启动原型生成一个新的 Maven 项目。
当执行 Util
作为单元测试时,它工作得很好(只是生成一个空的 Maven 项目)。
现在我想将这个小包装器集成到 Maven 插件中。但是当我执行 mojo(在第三个 Maven 项目中)时,MavenCli
的调用失败并出现异常:
[ERROR] Error executing Maven.
[ERROR] java.util.NoSuchElementException
role: org.apache.maven.eventspy.internal.EventSpyDispatcher
roleHint:
[ERROR] Caused by: null
实用程序如下所示:
public void createProject() {
final MavenCli cli = new MavenCli();
System.setProperty("maven.multiModuleProjectDirectory", "/usr/share/maven");
cli.doMain(new String[] { "archetype:generate", "-DgroupId=com.my.company",
"-DartifactId=hello-world", "-DarchetypeArtifactId=maven-archetype-quickstart",
"-DinteractiveMode=false" }, "/tmp", System.out, System.out);
}
util的相关依赖:
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
<version>3.3.9</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>3.3.9</version>
</dependency>
mojo 代码如下所示:
@Mojo(name = "custommojo", requiresProject = false)
public class CustomMojo extends AbstractMojo {
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
Util.createProject();
}
}
mojo 的 POM 仅包含对相关 Maven 工件(插件-api、插件注释等)和实用程序的依赖项。
我提到的第三个项目是一个空的 "maven-quickstart" 项目,它依赖于 mojo 项目和 mojo 在编译阶段执行的配置。
我不知道为什么它在单元测试的上下文中有效,但在 mojo 的上下文中却无效。
有人可以帮忙吗?
这是一个 class 加载问题。
MavenCli
将尝试从当前线程的上下文 classloader 加载 classes。在 Maven 插件内部,有 a special, restricted, classloader,它可以访问:
- 它自己的 classes;
- 在其依赖块中使用的 classes;
- 导出 classes 作为项目可能构建扩展的一部分;
- 从 Maven 核心和核心扩展导出 classes;
- 并且有 bootstrap classloader 作为父级。
但是,具体的 class org.apache.maven.eventspy.internal.EventSpyDispatcher
是 Maven 核心的一部分,但是 it is not part of the exported APIs(包 org.apache.maven.eventspy
没有列为 exportedPackage
) .所以插件无法加载 class。这也是它在您的测试中起作用的原因:您不在插件内部,因此 classloader 不同并且可以访问 class.
您甚至无法为插件显式添加对 maven-core
的依赖:它将被忽略,因为它应该已经提供了。
这里有2个解决方案:
- 不要在插件中使用 Maven Embedder API,而是 Invoker API。两者之间的区别在于 Invoker API 将在干净的环境中启动 Maven,与当前环境完全不同。由于它会重新开始一切,因此您不会遇到任何 class 加载问题。
- 使用 mojo-executor 库,它提供了一种从 Maven 插件中调用其他 Mojo 的简单方法。您可以在此处使用它来调用
archetype:generate
Mojo。
这在自定义 Maven 插件(使用 Maven 3.5.0)中对我有用:
ClassRealm classRealm = (ClassRealm) Thread.currentThread().getContextClassLoader();
MavenCli cli = new MavenCli(classRealm.getWorld());
cli.doMain( ... );
plexus Launcher sets the context class loader 到它的 ClassRealm
,它可以访问 "global" ClassWorld
。
不确定该解决方案有多稳定,但目前看来还不错。
二手进口:
import org.codehaus.plexus.classworlds.ClassWorld;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.apache.maven.cli.MavenCli;
@亚历山大:
你的回答让我朝着正确的方向前进,我在随后尝试 运行 doMain maven 命令时遇到了上述错误(一个命令会成功)。
但是你的代码给了我一个 ClassCast 异常:
Exception in thread "main" java.lang.ClassCastException: sun.misc.Launcher$AppClassLoader cannot be cast to org.codehaus.plexus.classworlds.realm.ClassRealm
at com.misys.tools.integration.codegen.cli.cmd.InstallApi.mavenInstallMchApi(InstallApi.java:58)
at com.misys.tools.integration.codegen.cli.cmd.InstallApi.run(InstallApi.java:50)
at com.misys.tools.integration.codegen.cli.OpenApiGen.main(OpenApiGen.java:26)
我设法将代码重写为:
MavenCli cli = new MavenCli(new ClassWorld("maven",Thread.currentThread().getContextClassLoader()));
然后嵌入式 maven 的 2 个后续 doMain 调用成功!