Изменить загрузчик классов

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

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" всегда использует систему classloader (можно определить, сравнивая переменные tcl и ccl во время отладки).

Можно ли сделать так, чтобы все классы, загружаемые новым потоком, использовали данный загрузчик классов?


person Chris    schedule 12.05.2010    source источник


Ответы (2)


Анонимный класс, который вы создаете с помощью 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();
person Patrick Schneider    schedule 14.05.2010

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

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

person G__    schedule 12.05.2010
comment
Хорошо, извините... Это важно в том смысле, что InjectingClassLoader — это класс, расширяющий org.apache.bcel.util.ClassLoader (с реализованным методом ModifyClass), который делает некоторые неприятные вещи с классом перед его загрузкой. AFAIK org.apache.bcel.util.ClassLoader переопределяет поведение цепочки загрузчиков классов по умолчанию таким образом, что измененный класс загружается до использования родительского загрузчика классов. - person Chris; 14.05.2010
comment
Только что проверил источник, и org.apache.bcel.utilClassloader по-прежнему расширяет java.lang.ClassLoader... - person G__; 14.05.2010
comment
Конструктор загрузчика классов по умолчанию использует ClassLoader.getSystemClassLoader(), а не Thread.currentThread().getContextClassLoader() в качестве загрузчика родительского класса. - person Brett Kail; 19.05.2010
comment
Ссылка на рисунок мертва. - person error; 17.05.2020