我熟悉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
为什么会这样?
答案 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}}。