我必须从一些JAR动态加载一个具有相同名称的类,但多次执行不同的实现。
我正在创建一个评估器后端,我必须动态加载类并测试它们。
测试是实例化应该测试的类的JUnit类,这是一个简单的例子:
package evaluator.tests;
import static org.junit.Assert.*;
import org.junit.*;
import evaluator.tested.*;
public class KTest {
private K tested;
@Before
public void setup() {
tested = new K();
}
@Test
public void returnsTrueTest() {
assertTrue(tested.returnsTrue());
}
}
我的应用程序的其余部分需要从用户接收JAR文件,这些文件将包含上面正在测试的K
类的实现。然后,KTest
必须在他们的 K
类上运行,而不是在应用程序中运行。
我知道如何动态加载一个类,但我不知道如何使用它进行测试,而不是我所做的那个。
我提出的一个解决方案是在一个全新的类中隔离测试,例如Evaluation
,在该类中创建一个新的类加载器,并使其加载所有引用的类。创建类加载器后,它将从JAR文件中加载K
类。
这意味着每次用户提交他的JAR时,都会实例化一个单独的Evaluation
,它会创建自己的类加载器并启动JUnit测试。当发生这种情况时,测试将使用用户的K
,的实现,而不是默认的。
这可能,怎么办呢?
我读过类加载器总是问他们的父是否已经加载了一个类。这意味着我必须以某种方式“刷新”我从Evaluation
中的JAR文件动态加载的所有类,以便它们被卸载然后再次加载到另一个Evaluation
中。
加载课程K
,测试课程K
,卸载课程K
,重复使用不同的K
。
答案 0 :(得分:4)
是的,你可以这样做。
例如,如果您使用java.net.URLClassLoader
将null作为父级:new URLClassLoader( urlArray , null )
。然后,引导程序ClassLoader
将用作ClassLoader
的父级。
这是一个示例类,它只使用一个新的类加载器,重新加载一个类。
package com.anarsoft.agent.regression;
import java.net.URL;
import java.net.URLClassLoader;
public class TestClassLoading {
public static boolean field = false;
public static void main(String[] args) throws Exception
{
URL[] urlArray = new URL[] { TestClassLoading.class.getProtectionDomain().getCodeSource().getLocation().toURI().toURL() };
URLClassLoader firstClassloader = new URLClassLoader( urlArray , null );
Class firstClass = firstClassloader.loadClass("com.anarsoft.agent.regression.TestClassLoading");
firstClass.getField("field").setBoolean(null,true);
System.out.println(firstClass.getField("field").getBoolean(null)); // true
URLClassLoader secondClassloader = new URLClassLoader( urlArray , null );
Class secondClass = secondClassloader.loadClass("com.anarsoft.agent.regression.TestClassLoading");
System.out.println(secondClass.getField("field").getBoolean(null)); // false
// the static field is false since its a new loaded class
}
}
答案 1 :(得分:1)
我在这个主题上写了一篇博文,其中对问题进行了进一步的解释:http://yannbane.com/2014/01/manipulating-java-class-loading-mechanisms/。
经过一番研究后,我想我找到了一种方法来完成我想要的东西。我还没有实现这个系统(一旦我做了就会编辑答案),所以我希望得到一些在这方面经验丰富的人的反馈。
这就是我理解URLClassLoaders(以及一般的类加载器)的工作方式:
如果 URLClassLoader.loadClass()
是当前的类加载器(对于执行的方法/类),则会自动调用它。首先将调用委托给其父类加载器,如果找不到任何内容,则使用自己的自定义findClass()
将其加载到其中的一个URL。 loadClass()
这里(以及类加载逻辑)只是从常规ClassLoader
继承而来。
此方法(loadClass()
)的默认行为是首先将搜索委托给父级,如果父级找不到该级别,则只能调用自己的findClass()
。默认情况下(findClass()
),此方法(ClassLoader
)未实现,您应该自己实现它,基本上从某处获取类的字节码(例如文件或文件)网络)并在其上调用defineClass()
。
在某个类上调用defineClass()
后,您才注册为此类的类加载器。在所有其他情况下,类加载被委托给您的父级(并且您不是您正在加载的类的类加载器,矛盾的是),或者您抛出ClassNotFoundException
。你不能在运行时更改某个类的类加载器,它在加载后设置,并且是常量。
我的所有测试类都会尝试getClass().getClassLoader().loadClass()
他们引用的所有类 - 包括我的自定义测试类(这是所有类的常规行为,而不仅仅是我的测试,要清楚)。只要他们使用标准类和我的应用程序中不是待测试类的其他类,就应该将此类加载方法进一步委托给应用程序类加载器。但是,一旦他们尝试加载一个待测试的类,他们就需要获得自己的,特别加载的自定义版本。
我的应用程序的用例是用户使用某个类提交JAR,然后在该JAR的类上运行期望该类具有某些方法的测试(使用JUnit) ,然后将结果发送回用户。
解决方案如下:
URLClassLoader
并重新处理类加载逻辑。
ClassNotFoundException
。ClassNotFoundException
(它实际上将被父级抛出)。URLClassLoader
defineClass()
,它将此自定义URLClassLoader
设置为测试类的父级loadClass()
。首先,他们将搜索URL - 因此,如果他们引用了一个待测试的类,它将从那里加载。如果它们引用了一些其他应用程序类或系统类,则自定义类加载逻辑将委托此调用,并且将返回已经加载的(可能)类。正如我所说 - 我还没有实现这个,如果你在评论中指出我的错误,我真的很想。
我从中收集了这些信息的资源:
答案 2 :(得分:0)
您需要确保您正在加载类的jar对于JVM系统类加载器是未知的(不在类路径上)。然后系统类加载器将不会从jar加载类,从而使您的自定义类加载器从此jar加载。
答案 3 :(得分:0)
你可以欺骗它。