как загрузчики классов Java работают в обычных условиях (неявное использование загрузчиков классов)

Я изучаю динамическую модификацию пути к классам. Я нашел одно решение, которое хорошо работает, но делает это с помощью явного вызова добавить URL(). (предположительно при запуске)

Однако я хотел бы перехватить процесс загрузки классов во время выполнения, чтобы найти классы, если загрузчик классов по умолчанию не может их найти. Я попытался создать подкласс ClassLoader, чтобы он просто делегирует findClass() и loadClass() значениям по умолчанию и распечатывает строку отладки, сообщающую мне, что эти методы были вызваны, но они, кажется, никогда не вызываются, когда мой класс использует зависимые классы через неявную загрузку классов, например

// regular object instantiation with 'new'
BrowserLauncher launcher;
launcher = new BrowserLauncher();

// static methods
Foobar.doSomethingOrOther();

// Class.forName()
Class cl = Class.forName("foo.bar.baz");

// reflection on a Class object obtained statically
Class<Foobar> cl = Foobar.class;
// do something with cl, like call static methods or newInstance()

Как работает загрузка классов в этих обстоятельствах? (по сравнению с более простым случаем, когда Classloader.loadClass () вызывается явно)

Вот моя попытка создать собственный загрузчик классов ниже. Если я использую DynClassLoader0.main () со списком аргументов {"some.package.SomeClass", "foo", "bar", "baz"}, а some.package.SomeClass ссылается на другие классы, найденные во внешних файлах .jar, используя один из методов, перечисленных выше, почему мои DynClassLoader0 не найдутClass () и loadClass () позвонить? Единственный раз, когда вызывается loadClass, - это явный вызов loadClass в функции main () ниже.

package com.example.test.classloader;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class DynClassLoader0 extends ClassLoader {
    public DynClassLoader0()
    {
        super();
    }
    public DynClassLoader0(ClassLoader parent)
    {
        super(parent);
    }
    public void runMain(String classname, String[] args) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException
    {
    // [***] here we explicitly use our classloader.
        Class<?> cl = loadClass(classname);
        Method main = cl.getMethod("main", String[].class);
        main.invoke(null, new Object[] {args});
    }

    @Override protected Class<?> findClass(String name) throws ClassNotFoundException
    {
        System.out.println("findClass("+name+")");
        return super.findClass(name);
    }

    @Override public Class<?> loadClass(String name) throws ClassNotFoundException
    {
        System.out.println("loadClass("+name+")");
        return super.loadClass(name);
    }

    static public void main(String[] args)
    {
        // classname, then args
        if (args.length >= 1)
        {
            String[] classArgs = new String[args.length-1];
            System.arraycopy(args, 1, classArgs, 0, args.length-1);

            ClassLoader currentThreadClassLoader
             = Thread.currentThread().getContextClassLoader();
            DynClassLoader0 classLoader = new DynClassLoader0(currentThreadClassLoader);
            // Replace the thread classloader - assumes
            // you have permissions to do so
            Thread.currentThread().setContextClassLoader(classLoader);

            try {
                classLoader.runMain(args[0], classArgs);
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
        else
        {
            System.out.println("usage: DynClassLoader {classname} [arg0] [arg1] ...");
        }
    }
}

edit: Я уже рассмотрел эти вопросы:

edit: Я подумал, что то, что kdgregory говорит ниже, правильно, что как только я использую свой загрузчик классов явно (см. строку в коде с [***] в качестве комментария), весь код, выполняемый из этого класса, вызовет неявную загрузку классов из того же загрузчика классов. Однако мой DynClassLoader0.loadClass () никогда не вызывается, кроме как во время самого внешнего явного вызова.


person Jason S    schedule 19.08.2009    source источник
comment
Я только что понял, почему вы не видите того, чего ожидаете: ваш DynClassLoader0 на самом деле не загружает никаких классов. Вы создаете подкласс от ClassLoader, а затем просто делегируете супер-реализацию. Но сам ClassLoader не загружает классы, он делегирует их вверх по дереву. Таким образом, классы, которые вы пытаетесь загрузить, находятся где-то еще в пути к классам и фактически загружаются каким-либо другим загрузчиком классов.   -  person kdgregory    schedule 19.08.2009


Ответы (2)


Цитата из ClassLoader JavaDoc:

Методы и конструкторы объектов, созданные загрузчиком классов, могут ссылаться на другие классы. Чтобы определить упомянутые классы, виртуальная машина Java вызывает метод loadClass загрузчика классов, который изначально создал класс.

Другими словами, как только вы загружаете класс, этот класс пытается загрузить другие классы через загрузчик классов, который его загрузил. В обычном приложении Java это системный загрузчик классов, который представляет путь к классам, переданный JVM, или загрузчик классов, используемый для загрузки среды выполнения JVM.

В зависимости от ваших потребностей существует вариант Class.forName (), который принимает загрузчик классов в качестве аргумента. Если вы используете это для загрузки определенного класса, тогда ссылки внутри этого класса должны использовать указанный загрузчик классов.


Изменить: я начал отслеживать ваш пример, но решил, что будет проще дать свой собственный. Если вы собираетесь написать свой собственный загрузчик классов, я предлагаю начать с существующего URLClassLoader, потому что он обрабатывает множество закулисных вещей.

Итак, MyClassLoader берет один каталог JARfile / и загружает классы только для этого каталога. Я переопределил три метода, вызываемых для загрузки класса, и просто зарегистрировал их вызов (используя System.err, потому что он не буферизует вывод, в отличие от System.out).

В моем примере используется библиотека, над которой я сейчас работаю; это было удобно, но вы можете выбрать любую библиотеку, если она еще не указана в вашем пути к классам.

Метод main () загружает класс через MyLoader. Затем я вызываю метод этого класса таким образом, который, как я знаю, вызовет исключение, которое также является частью библиотеки. Обратите внимание, что я вызываю метод путем отражения: поскольку библиотеки нет в моем пути к классам Eclipse, я не смог скомпилировать ее с явной ссылкой.

Когда я запускаю эту программу (в Sun JDK 1.5 для Linux), я вижу много вызовов loadClass () как для классов в моей библиотеке, так и для классов в пути к классам. Это ожидаемо: класс ParseUtil ссылается на множество других классов и будет использовать MyLoader (то есть свой загрузчик классов) для их загрузки. Для тех классов, которые MyLoader не может найти локально, он делегирует вверх дерево загрузчика.

Выдается исключение, и когда я распечатываю его загрузчик классов, я вижу, что он такой же, как созданный мной экземпляр MyLoader. Я также распечатываю загрузчик для Exception.class, и он равен нулю, а JavaDoc для Class.getClassLoader () указывает на загрузчик классов.

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;


public class ClassLoaderExample
{
    private static class MyClassLoader
    extends URLClassLoader
    {
        public MyClassLoader(String path)
        throws Exception
        {
            super(new URL[] { new File(path).toURL() });
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException
        {
            System.err.println("findClass(" + name + ")");
            return super.findClass(name);
        }

        @Override
        protected synchronized Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            System.err.println("loadClass(" + name + "," + resolve + ")");
            return super.loadClass(name, resolve);
        }

        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException
        {
            System.err.println("loadClass(" + name + ")");
            return super.loadClass(name);
        }
    }


    public static void main(String[] argv)
    throws Exception
    {
        ClassLoader myLoader = new MyClassLoader("/home/kgregory/Workspace/PracticalXml-1.1/target/classes/");
        System.out.println("myLoader = " + myLoader);

        Class<?> parseUtilKlass = myLoader.loadClass("net.sf.practicalxml.ParseUtil");
        Method parseMethod = parseUtilKlass.getDeclaredMethod("parse", String.class);

        try
        {
            parseMethod.invoke(null, "not at all valid XML");
        }
        catch (InvocationTargetException e)
        {
            Throwable ee = e.getCause();
            System.out.println("exception:" + ee);
            System.out.println("exception loader = " + ee.getClass().getClassLoader());

            System.out.println("Exception.class loader = " + Exception.class.getClassLoader());
        }
    }
}

Редактировать №2, основываясь на сегодняшних комментариях.

Ожидается, что загрузчик классов делегирует запросы своему родительскому элементу перед попыткой выполнить сам запрос (это находится в JavaDoc загрузчика классов). У этой практики есть несколько преимуществ, прежде всего в том, что вы не будете непреднамеренно загружать несовместимые экземпляры одного и того же класса.

Загрузчики классов J2EE изменяют эту модель: загрузчик классов, используемый для загрузки WAR, будет пытаться разрешить классы до загрузчика для содержащего EAR, который, в свою очередь, пытается разрешить классы до загрузчика классов контейнера. Целью здесь является изоляция: если и WAR, и его EAR содержат одну и ту же библиотеку, это, вероятно, потому, что им нужны разные версии (или у них небрежный процесс сборки). Я считаю, что даже в случае J2EE загрузчик классов контейнера выполняет делегирование стандартным способом.

person kdgregory    schedule 19.08.2009
comment
Другими словами, как только вы загружаете класс, этот класс пытается загрузить другие классы через загрузчик классов, который его загрузил. Я так думал, но похоже, что это не работает 8- (Я не могу сказать, что делаю не так. - person Jason S; 19.08.2009
comment
Спасибо - я попробую запустить вашу программу и посмотреть, чем моя отличается! - person Jason S; 19.08.2009
comment
Ах: Думаю, я могу понять суть проблемы. Если я использую ваш подход и загружаю класс, которого еще нет в пути к классам, он работает правильно и рекурсивно загружает все зависимые классы через загрузчик классов. Если я использую ваш подход и загружаю класс, который уже находится в пути к классам, он не загружает зависимые классы рекурсивно. - person Jason S; 20.08.2009
comment
Кто-нибудь знает, почему это так? - person Jason S; 20.08.2009

В вашем коде вызов super.loadClass() делегирует загрузку класса родительскому загрузчику классов (просто посмотрите на реализацию java.lang.ClassLoader#loadClass). Таким образом, не ваш экземпляр DynClassLoader0 загружает класс, а currentThreadClassLoader (который вы взяли из Thread.currentThread().getContextClassLoader()), который вы передали в качестве параметра конструктора в DynClassLoader0. И когда загруженный класс ссылается на другие классы, они также загружаются этим загрузчиком классов, а не вашим DynClassLoader0.

person Esko Luontola    schedule 19.08.2009
comment
JVM не обращается к загрузчику классов контекста для загрузки новых классов - я изначально думал, что это так, но JavaDoc ClassLoader говорит иначе, и тестовая программа терпит неудачу. - person kdgregory; 19.08.2009
comment
Я знаю, что делегирую родительскому загрузчику классов в моем DynClassLoader0.loadClass (), вызывая super.loadClass () - мой вопрос в том, почему не вызывается DynClassLoader0.loadClass (). - person Jason S; 19.08.2009
comment
ПОДОЖДИТЕ: Думаю, я начинаю понимать дилемму. Взгляните на цитату в ответе kdgregory (методы и конструкторы объектов, созданных загрузчиком классов, могут ссылаться на другие классы. Чтобы определить упомянутые классы, виртуальная машина Java вызывает метод loadClass загрузчика классов, который изначально создал class.) Есть ли способ использовать загрузчик классов по умолчанию для выполнения магии создания классов, но сообщить JVM, что не смотрите на мой загрузчик классов на предмет классов нижестоящих, которые, возможно, потребуется загрузить в будущем? - person Jason S; 19.08.2009