views:

322

answers:

2
+2  Q: 

Change classloader

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?

+1  A: 

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.

Greg Harman
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.
Chris
Just checked the source and org.apache.bcel.utilClassloader still extends java.lang.ClassLoader...
Greg Harman
The default class loader constructor uses ClassLoader.getSystemClassLoader(), not Thread.currentThread().getContextClassLoader(), as the parent class loader.
bkail
+3  A: 

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);
    thread.start();
Patrick Schneider
Nit: "reflectively". Given that there's confusion in the question regarding the context class loader, you might mention that setContextClassLoader has no effect unless the thread is performing some operation which requires it to be set (e.g., creating a SAXParser, performing a JNDI lookup, etc.).
bkail
Fixed; reflexively -> reflectively. Thanks.
Patrick Schneider