【问题标题】:Is it possible to "reset" a class loader?是否可以“重置”类加载器?
【发布时间】:2014-01-12 16:33:11
【问题描述】:

我必须多次从某个 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重复。

【问题讨论】:

    标签: java class junit classloader


    【解决方案1】:

    是的,你可以这样做。

    例如,如果您使用 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
    
    }
    

    }

    【讨论】:

    • 引导类加载器不“记住”它加载的类还是什么?
    • 参见维基百科:引导类加载器加载位于 /jre/lib 目录中的核心 Java 库[5]。这个类加载器是核心 JVM 的一部分,是用本机代码编写的。
    • 基本上,引导类加载器会加载 java... 包中的类。我认为不可能重新加载这些类,例如 java.lang.String 或 java.util.LinkedList
    • 我不确定我是否完全理解您的问题。我编辑了答案以给出一个重新加载类的例子
    • 由于我不完全理解这种方法,所以我创建了自己的方法。请查看我的answer 并滚动到“解决方案”。感觉就像我只是明确地实现了如果我只是使用你的解决方案就会发生的事情。您是否愿意评论您的解决方案比我的解决方案更好/不同?再次感谢,看来我错了,这确实回答了我的问题,但我不完全理解为什么或如何。现在你会得到 +1 :)
    【解决方案2】:

    我在这个主题上做了一个blog post,其中的问题得到了进一步的解释。


    经过一些研究,我想我已经找到了一种方法来完成我想要的。我还没有实现这个系统(我会编辑答案),所以我想从这方面更有经验的人那里得到一些反馈。

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

    URLClassLoader.loadClass() 如果是当前类加载器(用于执行的方法/类),则会自动调用。该调用首先委托给它的父类加载器,如果没有找到它,它会使用它自己的自定义findClass() 从它拥有的一个 URL 中加载它。这里的loadClass()(以及类加载逻辑)只是继承自常规的ClassLoader

    这个方法(loadClass())的默认行为是首先将搜索委托给父级,如果父级找不到该类,才调用自己的findClass()。这个方法(findClass())默认情况下(在ClassLoader中)是未实现的,你应该自己实现它,基本上是从某个地方(例如文件或网络)获取类的字节码并调用defineClass() 他们。

    一旦你在某个类上调用defineClass(),然后你才被注册为这个类的类加载器。在所有其他情况下,类加载要么委托给您的父级(自相矛盾的是,您不是您正在加载的类的类加载器),或者您抛出ClassNotFoundException。您不能在运行时更改某些类的类加载器,它是在加载后设置的,并且是常量。

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

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

    解决方法如下:

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

    正如我所说的 - 我还没有实现这个,如果你能指出我在 cmets 中的错误,我真的很想。

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

    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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 而自定义类加载器何时会“卸载”这些类?这很重要,因为必须隔离每个测试。加载类K,测试类K,卸载类K,用不同的K重复。
      • @yannbane 试试并告诉我们。
      【解决方案4】:

      你可以骗它。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-12-20
        • 2015-04-28
        • 2015-12-13
        • 1970-01-01
        • 2022-01-05
        • 2021-12-08
        • 2013-03-24
        • 1970-01-01
        相关资源
        最近更新 更多