在Maven插件中调用MavenCli失败

时间:2016-11-14 11:28:32

标签: java maven maven-plugin maven-embedder

我创建了一个小的util来包装MavenCli,它使用quickstart原型生成一个新的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

util看起来像:

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。

我提到的第三个项目是一个空的&#34; maven-quickstart&#34;与mojo项目有关系的项目以及在编译阶段执行mojo的配置。

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

有人可以帮忙吗?

3 个答案:

答案 0 :(得分:5)

这是一个类加载问题。

MavenCli将尝试从当前线程的上下文类加载器加载类。在Maven插件中,有a special, restricted, classloader,可以访问:

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

但是,特定类org.apache.maven.eventspy.internal.EventSpyDispatcher是Maven核心的一部分,但it is not part of the exported APIs(包org.apache.maven.eventspy未列为exportedPackage)。所以插件无法加载该类。这也是它在您的测试中有效的原因:您不在插件内部,因此类加载器不同并且可以访问该类。

你甚至无法为插件明确地添加对maven-core的依赖:它会因为它已被提供而被贬低。

这里有两个解决方案:

  • 不要在插件中使用Maven Embedder API,而是使用Invoker API。两者之间的区别在于Invoker API将在干净的环境中启动Maven,与当前版本完全不同。由于它重新启动所有内容,因此您不会遇到任何类加载问题。
  • 使用mojo-executor库,它提供了一种从Maven插件中调用其他Mojos的简便方法。您可以在此处使用它来调用archetype:generate Mojo。

答案 1 :(得分:1)

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

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

plexus启动器sets the context class loaderClassRealm,可以访问&#34;全球&#34; ClassWorld

不确定该解决方案的稳定性,但到目前为止还不错。

二手进口商品:

import org.codehaus.plexus.classworlds.ClassWorld;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.apache.maven.cli.MavenCli;

答案 2 :(得分: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调用成功!