将类传递给Maven2插件

时间:2012-03-15 16:54:27

标签: java class plugins maven-2 mapping

我熟悉Maven插件开发,我需要一个问题的帮助。

正如我所知,将复杂对象传递给maven插件非常容易,但是传递一个类呢? 让我们说我编写自己的插件,只包含2个类。

Message.java

package abstractmessage;
public abstract class Message {
    public abstract String getMessage();
}

MessageMojo.java

/**
 * @goal message
 */
public class MessageMojo extends AbstractMojo {
    /**
     * @parameter expression="${message.messageClass}"
     */
    private String messageClass ;

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        Message message = null;

        try {
            Class<?> clazz = Class.forName(messageClass);
            message = (Message) clazz.newInstance();
        } catch (Exception e) {
            getLog().error(e);
        }

        getLog().info(message.getMessage());
    }
}

因此插件需要扩展Message类的名称并尝试动态初始化它。

真正的Maven-Project中的扩展Message类看起来像这样。

package message;
import abstractmessage.Message;

public class RealMessage extends Message {
    @Override
    public String getMessage() {
        return "Hello plugin. From MavenProject.";
    }
}

pom.xml使用插件。

<build>
    <plugins>
        <plugin>
            <artifactId>message-plugin</artifactId>
            <groupId>message-plugin</groupId>
            <version>1.0</version>
            <executions>
                <execution>
                    <phase>test</phase>
                    <goals>
                        <goal>message</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <messageClass>message.RealMessage</messageClass>
            </configuration>
        </plugin>
    </plugins>
</build>

<dependencies>
    <dependency>
        <artifactId>message-plugin</artifactId>
        <groupId>message-plugin</groupId>
        <version>1.0</version>
    </dependency>
</dependencies>

但是当我执行maven时:test我得到一个java.lang.ClassNotFoundException: message.RealMessage

为什么会这样?

2 个答案:

答案 0 :(得分:1)

您的插件的类加载器无法访问项目的类。它只能访问自己的类及其依赖项。

您有两种选择。

选项一是创建一个新的类加载器,从当前项目及其依赖项加载类。 surefire plugin是一个执行此操作的插件示例 - 它可以加载已编译的单元测试类,并在它不处于fork模式时运行它们。

从依赖项创建一个新的类加载器并不是一件容易的事,但是首先你要将@requiresDependencyResolution test放在你的Mojo上,并获取项目的依赖项(以及项目的构建输出目录)如果要在搜索中包含项目的类,并从这些JAR和目录构造URLClassLoader。 Surefire的generateTestClasspath() method可能有助于作为起点。

另一种选择是在一个单独的项目中编译你的RealMessage类,构建一个单独的JAR并将其作为对消息插件的依赖包含。

在这种情况下,您的构建部分类似于:

<build>
    <plugins>
        <plugin>
            <artifactId>message-plugin</artifactId>
            <groupId>message-plugin</groupId>
            <version>1.0</version>
            <executions>
                <execution>
                    <phase>test</phase>
                    <goals>
                        <goal>message</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <messageClass>message.RealMessage</messageClass>
            </configuration>
            <dependencies>
                <groupId>message-plugin-additions</groupId>
                <artifactId>message-plugin-additions</artifactId>
                <version>1.0</version>
            </dependencies>
        </plugin>
    </plugins>
</build>

message-plugin-additions项目包含message.RealMessage类。如果您愿意使用单独的项目,这可能比第一个选项简单得多。

答案 1 :(得分:1)

我尝试构建一个新的URLClassLoader并将其链接到当前的ClassLoader。

@Override
public void execute() throws MojoExecutionException, MojoFailureException {
    //a direct absolute path just for testning
    chainClassLoader(new File("C:\\Users\\admin\\.m2\\repository\\message-plugin\\real-message\\1.0\\real-message-1.0.jar"));
    Message message = null;

    try {
        Class<?> clazz = Class.forName(messageClass);
        message = (Message) clazz.newInstance();
    } catch (Exception e) {
        getLog().error(e);
    }

    getLog().info(message.getMessage());
}

private void chainClassLoader(File file) throws Exception {
        ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{file.toURI().toURL()}, currentClassLoader);
        Thread.currentThread().setContextClassLoader(urlClassLoader);
}

但我仍然得到ClassNotFoundException。

另外,我如何“使用@requiresDependencyResolution”抓住项目的依赖性?

修改

好的,我想我明白了。插件中的ClassLoader层次结构如下所示。

null
  sun.misc.Launcher$ExtClassLoader
    sun.misc.Launcher$AppClassLoader

null
  org.codehaus.classworlds.RealmClassLoader
    java.net.URLClassLoader // my chained classloader

Class.forName(clazz)尝试从AppClassLoader开始查找clazz,该ExtClassLoader将其委托给其父URLClassLoader。但是,因为我将新的类路径添加到ClassNotFoundException,这显然是在另一个ClassLoader层次结构中,我得到Class.forName(clazz)

解决方案不是使用Thread.currentThread().getContextClassLoader().loadClass(clazz),而是使用URLClassLoader,因为我将ContextClassLoader链接到了线程的{{1}}。