是否可以"重置"类加载器?

cor*_*zza 8 java junit class 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());
    }

}
Run Code Online (Sandbox Code Playgroud)

我的应用程序的其余部分需要从用户接收JAR文件,这些文件将包含K上面正在测试的类的实现.然后,KTest必须运行他们的 K类,而不是应用程序中的类.

我知道如何动态加载一个类,但我不知道如何使用它进行测试,而不是我所做的那个.

我提出的解决方案之一是在一个全新的类中隔离测试,例如Evaluation,在该类中创建一个新的类加载器,并使其加载所有引用的类.在创建类加载器之后,它将K从JAR文件加载该类.

这意味着每次用户提交他的JAR时,Evaluation都会实例化一个单独的,它会创建自己的类加载器并启动JUnit测试.当发生这种情况时,测试将使用用户的实现K,而不是默认的实现.

这可能吗,怎么办呢?

我读到类加载器总是问他们的父是否已经加载了一个类.这意味着我必须以某种方式"刷新"我从JAR文件中动态加载的所有类Evaluation,以便它们将被卸载然后再次加载到另一个中Evaluation.

加载类K,测试类K,卸载类K,重复不同K.

cor*_*zza 1

我已经就这个主题发表了一篇博文,其中对问题进行了进一步的解释。

\n\n
\n\n

经过一番研究,我想我已经找到了一种方法来完全实现我想要的目标。我还没有实现这个系统(一旦实现就会编辑答案),所以我希望得到在这方面更有经验的人的一些反馈。

\n\n

这就是我理解 URLClassLoaders(以及一般的类加载器)工作方式的方式:

\n\n

URLClassLoader.loadClass()如果它\xe2\x80\x99是当前类加载器(用于执行方法/类),则会自动调用。该调用首先被委托给其父类加载器,如果没有找到任何内容,它会使用自己的自定义从findClass()它拥有的 URL 之一加载它。loadClass()这里(和类加载逻辑)只是从常规的ClassLoader.

\n\n

此方法 ( ) 的默认行为loadClass()是首先将搜索委托给父级,如果父级找不到该类,则仅调用其自己的findClass(). findClass()默认情况下(在 )中,此方法 ( )ClassLoader未实现,您应该自己实现它,方法是从某处(例如文件或网络)获取类的字节码并调用defineClass()它们。

\n\n

一旦你调用defineClass()了某个类,只有那时你才被注册为该类的类加载器。在所有其他情况下,类加载要么委托给您的父级(矛盾的是,您不是正在加载的类的类加载器),要么抛出ClassNotFoundException. 您无法在运行时更改某些类的类加载器,它在加载后就已设置,并且是常量。

\n\n

我的所有测试类将尝试getClass().getClassLoader().loadClass()它们引用的所有类 - 包括我的自定义测试类(这是所有类的常规行为,而不仅仅是我的测试,需要明确)。只要它们\xe2\x80\x99使用标准类,以及我的应用程序中不是待测试类的其他类,就应该将此类加载方法进一步委托给应用程序类加载器。然而,一旦他们尝试加载要测试的类,他们就需要获取自己的、专门加载的自定义版本。

\n\n

我的应用程序的用是,用户提交带有某个类的 JAR,然后对该 JAR 中的类运行期望此类具有某些方法的测试(使用 JUnit),然后将结果发送回用户。

\n\n

解决方法如下:

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

正如我\xe2\x80\x99ve所说 - 我还没有\xe2\x80\x99t实现这个,我真的很希望你\xe2\x80\x99d在评论中指出我的错误。

\n\n

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

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