使用ClassLoader定义同一个类两次

时间:2014-02-02 07:01:11

标签: java classloader

我正在开发一个项目,它涉及读取给定JAR文件中的所有类,修改这些类的部分(在字节码级别,使用ASM),并创建一个ClassLoader这些修改过的类。目前,在修改类之后,我创建了ClassLoader类的自定义实例,使defineMethod(String, byte[])可见。

以下是自定义ClassLoader的代码(它是嵌套类):

private static class ExposingClassLoader extends ClassLoader {

    private ExposingClassLoader(JarFile jar) throws MalformedURLException {
        super(new URLClassLoader(new URL[] { new URL("jar:" + new File(jar.getName()).toURI().toURL() + "!/") }));
    }

    private void defineClass(String name, byte[] data) {
        defineClass(name, data, 0, data.length);
    }
}

您可能已经注意到我将父类加载器设为URLClassLoader。当我构造自定义类加载器时,我使URLClassLoader引用包含未修改的类文件的JAR。在修改了一些类文件之后,我遍历它们并为每个文件调用defineMethod(String, byte[])。一些修改后的类相互依赖,即A类可能是B类的超类,或者C类可能实现D类,或者E类可能包含对F类的引用,等等

对于我的测试,我使用两个类。第一个名为fn,第二个名为fifi名为fn,包含多个fi字段。这两个类都存在于用于构造ClassLoader的JAR文件中,唯一的区别是它们未经修改。出于某种原因,如果我定义类fn它工作正常(它不应该?JAR文件中已存在类fn)。但是,如果我尝试定义fi,再次多次引用fn,我会遇到以下异常:

Exception in thread "main" java.lang.LinkageError: loader (instance of Injector$ExposingClassLoader): attempted  duplicate class definition for name: "fn"

在线:

defineClass(name, data, 0, data.length);

我不知道如何处理这件事。最简单的方法是“卸载”JAR中包含的类,但只包括我修改过的那些类。这样,当我定义修改后的类时,它们将不会被加载到ClassLoader中。但是,我环顾四周,我还没有找到一个干净的方法来做到这一点。有人说我需要单独的(多个)类加载器,这对我的项目不起作用,除非有一种方法可能将这些多个类加载器组合成一个,就像包装器一样。

如何重新定义ClassLoader中的课程?

1 个答案:

答案 0 :(得分:2)

我找到了解决问题的方法。基本上,我应该覆盖defineClass而不是覆盖findClass的行为。这是有效的代码。

public class ModifiableClassLoader extends ClassLoader {

    private final Map<String, byte[]> definitions;

    public ModifiableClassLoader(JarFile jar, Map<String, byte[]> definitions) throws MalformedURLException {
        super(new URLClassLoader(new URL[] { new URL("jar:" + new File(jar.getName()).toURI().toURL() + "!/") }));
        this.definitions = definitions;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classBytes = definitions.remove(name);
        if (classBytes != null) {
            return defineClass(name, classBytes, 0, classBytes.length);
        }
        return super.findClass(name);
    }
}

它根据另一个我现在找不到的Stack Overflow问题的答案写了它。我以前一定忽略了它。