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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With