Изменить загрузчик классов - PullRequest
11 голосов
/ 12 мая 2010

Я пытаюсь переключить загрузчик классов во время выполнения:

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 ]

16 голосов
/ 14 мая 2010

Анонимный класс, который вы создаете с помощью 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();
1 голос
/ 12 мая 2010

Я думаю, что InjectingClassLoader здесь может быть важен. Вспомните, как работает делегирование загрузки классов - если более чем один загрузчик классов в вашей иерархии может найти класс, то загружается самый верхний загрузчик классов. (См. Рисунок 21.2 здесь )

Поскольку InjectingClassLoader не указывает родителя в своем конструкторе, он по умолчанию будет использовать конструктор в абстрактном ClassLoader, который установит текущий контекстный загрузчик классов в качестве родителя InjectingClassLoader. Поэтому, поскольку родитель (старый загрузчик классов контекста) может найти TestProxy, он всегда загружает класс, прежде чем InjectingClassLoader получит шанс.

...