3

有没有办法将非核心 Java 类返回到新加载的状态?我想要相当于卸载一个类并从头开始重新加载它。我最关心的是静态初始化器和变量。

问题背景:我正在为学生代码编写一个机器人评分器。我见过的一个常见的学生错误是不恰当地使用静态变量。例如,考虑一个包含静态元素计数的 Collection。该集合在第一次创建和使用时会正常工作,但在下一次实例化时会失败。如果我希望我的测试尽可能模块化,我需要一种在测试后恢复干净状态的方法。

现在我或多或少地像这样加载这个类,我已经勾勒出我想如何使用它。

    String classUnderTest = "package.class";
    Class unitUnderTest;
    try {
        unitUnderTest = Class.forName(classUnderTest);
    } catch (ClassNotFoundException e) {
        System.out.println("Class \"" + classUnderTest + "\" was not found, can't continue.");
        printGradeAndExit();
    }
    // Run foundation tests (stuff on which later tests depend) using unitUnderTest.newInstance()
    runFoundationTests(unitUnderTest);
    // Now reset unitUnderTest for a static variable detection test
    lookForCommonStaticVariableMistakes(unitUnderTest);

显然,robo-graders 不可能是完美的,但我想找出常见的错误,这就是其中之一。

根据Java 语言规范第 12.7 节,支持卸载类是可选的(但会做我想要的)。有没有办法在不依赖非标准功能的情况下做到这一点?

最后的手段是做一个 Ant 构建,它在单独的程序中运行一系列测试,但如果可能的话,我想在一个进程中完成这项工作。

4

2 回答 2

3

You don't necessarily have to unload the class for each run just use a new classloader. You can create a new instance of a URLClassLoader and load your students code using that (as opposed to putting your students code on the regular application classpath). Load the target class using that ClassLoader and then find a method of your choosing using the returned Class object and invoke it. If the method you wish to invoke is non-static you will have to first take a few extra steps to create an instance of the object via reflection.

Example using a typical public static void main(String[]) method for entry.

String[] args = new String[0]; //Add arguments for main call.

//Add whatever classpath elements you need.
String[] classpath = {"file:///example/path/to/studentProjectJarFile.jar", 
                      "file:///example/path/to/studentProjectDir/"};

//Create classloader.
ClassLoader cl = 
            new URLClassLoader(classpath);

Class<?> mainClazz;
Method mainMethod;

//Find Class.
mainClazz = cl.loadClass("com.example.MyClass"); 
//Find the target method.
mainMethod = mainClazz.getMethod("main", String[].class); 
//Invoke method.
mainMethod.invoke(null, (Object) args); 

In the this example com.example.MyClass should not be on the regular application classpath. If it is then that is the version that will get used instead of the one found by the custom classloader as standard classloaders use a parent-first delegation for loading classes.

于 2012-03-01T03:29:26.440 回答
2

坦率地说,我会启动一个新的 JVM 来评估每个学生的代码。如果您尝试在一个 JVM 实例中完成所有操作,那么一个学生的代码有太多机会干扰另一个学生的代码。像无限循环一样简单的东西......

作为副作用,这在很大程度上消除了对自定义类加载器等的需求。

于 2012-03-01T04:24:19.770 回答