是否有可能重置"一个类加载器?

时间:2013-12-24 12:14:38

标签: java class junit classloader

我必须从一些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

4 个答案:

答案 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) ,然后将结果发送回用户。

解决方案如下:

  1. 实现待测试类的基本版本,以便在没有任何提交的JAR的情况下编译和运行测试。这可能(更有可能)应该通过利用多态来完成,但目前还没有计划(这意味着用户在发送要测试的类之前,最有可能在本地扩展类的“​​基本”版本)。
  2. 扩展URLClassLoader并重新处理类加载逻辑。
    • 执行默认实现中存在的必要检查(已加载的类)。
    • 尝试自己查找课程,如果它不在您的网址中,则抛出ClassNotFoundException
    • 如果已经加载了该类,或者刚刚从某个URL加载了该类,则返回该类。
    • 如果之前(通过此类加载器)尚未加载该类,也不在其中一个URL中,则将搜索委托给您的父级。
    • 如果父级返回该类,则返回该类,如果不是,则返回ClassNotFoundException(它实际上将被父级抛出)。
  3. 对于每个发送类的JAR文件:
    • 实例化自定义URLClassLoader
    • 将JAR添加到它以及特定的测试类
    • 要求它加载测试类。此时我的自定义类加载逻辑启动,它直接从磁盘加载我的测试 - 重新加载,而不委托给它的父类加载器。为什么?它在我的测试类上调用defineClass(),它将此自定义URLClassLoader设置为测试类的父级
    • 将测试类提供给JUnit,然后JUnit将其实例化并开始测试
  4. 一旦我的某个测试正在运行,每次他们引用任何课程时,他们都会调用自己的自定义类加载器loadClass()。首先,他们将搜索URL - 因此,如果他们引用了一个待测试的类,它将从那里加载。如果它们引用了一些其他应用程序类或系统类,则自定义类加载逻辑将委托此调用,并且将返回已经加载的(可能)类。
  5. 正如我所说 - 我还没有实现这个,如果你在评论中指出我的错误,我真的很想。

    我从中收集了这些信息的资源:

    1. http://www.onjava.com/pub/a/onjava/2003/11/12/classloader.html
    2. http://www.devx.com/Java/Article/31614
    3. http://www.onjava.com/pub/a/onjava/2005/01/26/classloading.html
    4. http://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html#loadClass%28java.lang.String,%20boolean%29

答案 2 :(得分:0)

您需要确保您正在加载类的jar对于JVM系统类加载器是未知的(不在类路径上)。然后系统类加载器将不会从jar加载类,从而使您的自定义类加载器从此jar加载。

答案 3 :(得分:0)

你可以欺骗它。

  1. 要重置的Cass不应该在类路径中
  2. 可以实现接口或扩展类路径中的类(对于类型转换很有用或者需要使用反射来'发现'和调用方法,或者调用类调用另一个具有调用类的代码的方法( es)正在测试中
  3. 每次要测试时,使用URL类加载器子类重命名jar,从jar加载。在你的加载器初始化时,null就可以了。
  4. 因此,如果您第一次加载a1.jar,请从下次加载a2.jar并确保将a1.jar移动到另一个文件夹