3

我创建了一个小 util 来 wrap 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 工件(plugin-api、plugin-annotation 等)和 util.xml 的依赖项。

我提到的第三个项目是一个空的“maven-quickstart”项目,它依赖于 mojo-project 和 mojo 在编译阶段执行的配置。

我不知道为什么它在单元测试的上下文中起作用,但在 mojo 的上下文中却不起作用。

有人可以帮忙吗?

4

3 回答 3

5

这是一个类加载问题。

MavenCli将尝试从当前线程的上下文类加载器加载类。在 Maven 插件内部,有一个特殊的受限类加载器,它可以访问:

  • 它自己的类;
  • 在其依赖项块中使用的类;
  • 导出的类作为项目可能的构建扩展的一部分;
  • 从 Maven 核心和核心扩展导出的类;
  • 并将引导类加载器作为父级。

但是,特定类org.apache.maven.eventspy.internal.EventSpyDispatcher是 Maven 核心的一部分,但它不是导出 API的一部分(包org.apache.maven.eventspy未列为exportedPackage)。所以插件无法加载该类。这也是它在您的测试中起作用的原因:您不在插件内部,因此类加载器是不同的并且可以访问该类。

您甚至无法摆脱maven-core对插件的显式依赖:它将被丢弃,因为它应该已经提供了。

这里有2个解决方案:

  • 不要在插件中使用 Maven Embedder API,而是使用Invoker API。两者的区别在于 Invoker API 将在干净的环境中启动 Maven,与当前环境完全不同。因为它重新开始一切,你不会有任何类加载问题。
  • 使用mojo-executor库,它提供了一种从 Maven 插件中调用其他 Mojo 的简单方法。您可以在此处使用它来调用archetype:generateMojo。
于 2016-11-14T14:16:45.377 回答
1

这适用于我在自定义 maven 插件中(使用 Maven 3.5.0):

ClassRealm classRealm = (ClassRealm) Thread.currentThread().getContextClassLoader();
MavenCli cli = new MavenCli(classRealm.getWorld());
cli.doMain( ... );

plexus 启动器将上下文类加载器设置为其ClassRealm,它可以访问“全局” ClassWorld

不确定该解决方案有多稳定,但到目前为止看起来不错。

二手进口:

import org.codehaus.plexus.classworlds.ClassWorld;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.apache.maven.cli.MavenCli;
于 2017-12-07T08:21:19.367 回答
0

@亚历山大:

你的回答让我朝着正确的方向前进,我在随后尝试运行 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 调用成功!

于 2018-12-20T22:58:52.303 回答