Я пытаюсь переключить загрузчик класса во времени выполнения:
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. ClassLoder, который должен загрузить измененные версии классов прежде, чем попросить, чтобы это был родитель для них),
Я хотел бы сделать результат из "подхода 1", и "приближаются 2" точно то же, но похоже, что thread.setContextClassLoader (classLoader) ничего не делает, и "подход 2" всегда использование система classloader (может быть определен путем сравнения tcl и ccl переменные при отладке).
Действительно ли возможно сделать все классы загруженными данным classloader использования нового потока?
Анонимный класс, который вы создаете через new Thread("test") { ... }
имеет неявную ссылку на вложенный экземпляр. Литералы классов в этом анонимном классе будут загружаться с помощью ClassLoader объемлющего класса.
Для того, чтобы этот тест сработал, вы должны найти подходящую реализацию Runnable и загрузить его рефлексивно, используя нужный ClassLoader; затем передать его явно в поток. 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();
Я думаю, что InjectingClassLoader может быть важен здесь. Помните, как работает делегирование загрузки классов - если более одного загрузчика классов в вашей иерархии могут найти класс, то загружаться будет самый верхний загрузчик. (См. рисунок 21.2 здесь)
Поскольку InjectingClassLoader не указывает родителя в своем конструкторе, он по умолчанию обратится к конструктору абстрактного ClassLoader, который установит текущий контекстный загрузчик классов в качестве родителя InjectingClassLoader. Поэтому, поскольку родитель (старый контекстный загрузчик классов) может найти TestProxy, он всегда загрузит класс до того, как InjectingClassLoader получит шанс.