简体   繁体   中英

Defining the same class twice using a ClassLoader

I am working a project which involves reading all of the classes in a given JAR file, modifying some of these classes (at the bytecode level, using ASM ), and creating a ClassLoader of these modified classes. Currently, after modifying the classes, I create a custom instance of the ClassLoader class which makes defineMethod(String, byte[]) visible.

Here is the code for the custom ClassLoader (it is a nested class):

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);
    }
}

You may have noticed that I made the parent class loader a URLClassLoader . When I construct the custom class loader, I make the URLClassLoader refer to the JAR that contains the unmodified class files. After modification of some of the class files, I iterate through them and invoke defineMethod(String, byte[]) for each one. Some of the modified classes depend on each other, that is, class A may be the superclass of class B, or class C may implement class D, or class E may contain references to class F, et cetera .

For my tests, I am using two classes. The first is named fn and the second is named fi , and fi extends fn and contains multiple fields of type fi . Both of these classes are present in the JAR file that is used to construct the ClassLoader , the only difference is that they are unmodified. For some reason, if I define class fn it works fine (which it should not? The class fn already exists in the JAR file). However, if I try to define fi , which, again, refers to fn multiple times, I get this exception:

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

On the line:

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

And I am not sure how to deal with this. The easiest way to do this would be to "unload" the classes that are contained in the JAR, but only those that I have modified. That way, when I define my modified classes, they will not already be loaded in the ClassLoader . However, I have looked around and I have yet to find a clean way to do it. Some have said that I need separate (multiple) class loaders, and that would not work for my project unless there was a way to perhaps combine these multiple class loaders into one, like a wrapper.

How can I redefine a class in a ClassLoader ?

I found the solution to my problem. Basically, rather than overriding the behavior of defineClass I should have overriden findClass . Here is the code that works.

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);
    }
}

It wrote it based on an answer to another Stack Overflow question which I cannot find right now. I must have overlooked it before.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM