I'm trying to switch the class loader at runtime:
public class Test {
public static void main(String[] args) throws Exception {
final InjectingClassLoader classLoader = new InjectingClassLoader();
Thread.currentThread().setContextClassLoader(classLoader);
Thread thread = new Thread("test") {
public void run() {
System.out.println("running...");
// approach 1
ClassLoader cl = TestProxy.class.getClassLoader();
try {
Class c = classLoader.loadClass("classloader.TestProxy");
Object o = c.newInstance();
c.getMethod("test", new Class[] {}).invoke(o);
} catch (Exception e) {
e.printStackTrace();
}
// approach 2
new TestProxy().test();
};
};
thread.setContextClassLoader(classLoader);
thread.start();
}
}
and:
public class TestProxy {
public void test() {
ClassLoader tcl = Thread.currentThread().getContextClassLoader();
ClassLoader ccl = ClassToLoad.class.getClassLoader();
ClassToLoad classToLoad = new ClassToLoad();
}
}
(InjectingClassLoader is a class extending the org.apache.bcel.util.ClassLoader which should load the modified versions of classes before asking it's parent for them)
I'd like to make the result of "approach 1" and "approach 2" exactly same, but it looks like thread.setContextClassLoader(classLoader) does nothing and the "approach 2" always uses the system classloader (can be determined by comparing tcl and ccl variables while debugging).
Is it possible to make all classes loaded by new thread use given classloader?
As we can see, there are three different class loaders here: application, extension, and bootstrap (displayed as null). The application class loader loads the class where the example method is contained. An application or system class loader loads our own files in the classpath.
The Java Class Loader is a part of the Java Runtime Environment that dynamically loads Java classes into the Java Virtual Machine. Usually classes are only loaded on demand. The Java run time system does not need to know about files and file systems as this is delegated to the class loader.
A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class.
The anonymous class you are creating via new Thread("test") { ... }
has an implicit reference to the enclosing instance. Class literals within this anonymous class will be loaded using the enclosing class's ClassLoader.
In order to make this test work, you should pull out a proper Runnable implementation, and load it reflectively using the desired ClassLoader; then pass that explicitly to the thread. Something like:
public final class MyRunnable implements Runnable {
public void run() {
System.out.println("running...");
// etc...
}
}
final Class runnableClass = classLoader.loadClass("classloader.MyRunnable");
final Thread thread = new Thread((Runnable) runableClass.newInstance());
thread.setContextClassLoader(classLoader); // this is unnecessary unless you you are using libraries that themselves call .getContextClassLoader()
thread.start();
I think InjectingClassLoader may be important here. Remember how classloading delegation works - if more than one classloader in your hierarchy can find the class, the top-most classloader will be the one that loads. (See Figure 21.2 here)
Since InjectingClassLoader doesn't specify a parent in its constructor, it will default to the constructor in the abstract ClassLoader, which will set the current context classloader as InjectingClassLoader's parent. Therefore, since the parent (old context classloader) can find TestProxy, it always loads the class before InjectingClassLoader gets a chance to.
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