как работают загрузчики классов Java для «обычных» обстоятельств (неявное использование загрузчиков классов) - PullRequest
5 голосов
/ 19 августа 2009

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

Однако я бы хотел перехватить процесс загрузки классов во время выполнения, чтобы найти классы, если загрузчик классов по умолчанию не может их найти. Я попытался создать подкласс 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 не находит findClass () и 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 () никогда не вызывается, кроме как во время самого внешнего явного вызова.

Ответы [ 2 ]

4 голосов
/ 19 августа 2009

Цитировать из ClassLoader JavaDoc :

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

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

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


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

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

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

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

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

Исключение выдается, и когда я распечатываю его загрузчик классов, я вижу, что он совпадает с созданным мной экземпляром MyLoader. Я также распечатываю загрузчик для Exception.class, и он имеет значение null - что в 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, основываясь на сегодняшних комментариях.

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

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

1 голос
/ 19 августа 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...