Change classloader

喜夏-厌秋 提交于 2019-11-27 16:16:12

问题


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:


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



回答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.



来源:https://stackoverflow.com/questions/2822004/change-classloader

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!