更改类加载器

12

我正在尝试在运行时切换类加载器:

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

并且:

public class TestProxy {
    public void test() {
        ClassLoader tcl = Thread.currentThread().getContextClassLoader();
        ClassLoader ccl = ClassToLoad.class.getClassLoader();
        ClassToLoad classToLoad = new ClassToLoad();
    }
}

InjectingClassLoader是一个扩展自org.apache.bcel.util.ClassLoader的类,它应该在请求其父类加载类之前加载修改版本的类。

我想让“方法1”和“方法2”的结果完全相同,但看起来 thread.setContextClassLoader(classLoader) 没有任何作用,“方法2”始终使用系统类加载器(可以通过调试时比较tcl和ccl变量来确定)。

是否可能使新线程加载的所有类都使用给定的类加载器?

2个回答

17

您通过 new Thread("test") { ... } 创建的匿名类具有对封闭实例的隐式引用。在此匿名类中的类文本字面值将使用封闭类的ClassLoader进行加载。

为了使此测试正常工作,您应该提取出一个适当的Runnable实现,并使用所需的ClassLoader反射加载它;然后显式地将其传递给线程。例如:

    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
我认为InjectingClassLoader在这里可能很重要。记住类加载代理的工作原理 - 如果你的层次结构中有多个类加载器可以找到该类,则最顶部的类加载器将是加载该类的类加载器。(请参见此处的图21.2)

由于InjectingClassLoader在其构造函数中没有指定父级,因此它将默认使用抽象ClassLoader中的构造函数,该构造函数将当前上下文类加载器设置为InjectingClassLoader的父级。因此,由于父级(旧上下文类加载器)可以找到TestProxy,因此它总是在InjectingClassLoader有机会之前加载类。


好的,抱歉...它的相关性在于InjectingClassLoader是一个扩展org.apache.bcel.util.ClassLoader的类(具有实现modifyClass方法),它在加载类之前对类进行了一些不好的操作。据我所知,org.apache.bcel.util.ClassLoader覆盖了类加载器链的默认行为,以使修改后的类在使用父类加载器之前被加载。 - Chris
刚刚检查了源代码,org.apache.bcel.util.ClassLoader仍然继承自java.lang.ClassLoader... - G__
1
默认的类加载器构造函数使用ClassLoader.getSystemClassLoader()作为父类加载器,而不是Thread.currentThread().getContextClassLoader()。 - Brett Kail
图片链接已失效。 - error

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接