Change classloader
Asked Answered
K

2

11

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?

Kumamoto answered 12/5, 2010 at 19:14 Comment(0)
N
17

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();
Novosibirsk answered 14/5, 2010 at 6:49 Comment(0)
K
2

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.

Kimberli answered 12/5, 2010 at 19:39 Comment(4)
OK, sorry... It is relevant in the way that the InjectingClassLoader is a class extending the org.apache.bcel.util.ClassLoader (with implemented modifyClass method) which makes some nasty stuff with the class before it is loaded. AFAIK org.apache.bcel.util.ClassLoader is overriding the default behavior of classloaders chain in the way that the modified class is loaded before the parent classloader is used.Kumamoto
Just checked the source and org.apache.bcel.utilClassloader still extends java.lang.ClassLoader...Kimberli
The default class loader constructor uses ClassLoader.getSystemClassLoader(), not Thread.currentThread().getContextClassLoader(), as the parent class loader.Simulcast
Link to figure is dead.Turne

© 2022 - 2024 — McMap. All rights reserved.