Как мне создать ClassLoader для родительского последнего / дочернего первого в Java или Как переопределить старую версию Xerces, которая уже была загружена в родительский CL? - PullRequest
35 голосов
/ 27 марта 2011

Я хотел бы создать загрузчик классов «родитель-последний / ребенок-первый», например загрузчик классов, который сначала будет искать классы в загрузчике дочерних классов, и только потом делегировать его родительскому ClassLoader для поиска классов.

Пояснение:

Теперь я знаю, что для полного разделения ClassLoading мне нужно использовать что-то вроде URLClassLoader, передавая null в качестве родителя, благодаря этому ответу на мой предыдущий вопрос

Однако текущий вопрос помогает мне решить эту проблему:

  1. Мой код + зависимые фляги загружаются в существующую систему, используя ClassLoader, который устанавливает ClassLoader этой системы в качестве его родителя (URLClassLoader)

  2. Эта Система использует некоторые библиотеки версии, несовместимой с той, которая мне нужна (например, более старая версия Xerces, которая не позволяет мне запускать мой код)

  3. Мой код работает отлично, если работает автономно, но не работает, если запускается из этого ClassLoader

  4. Однако мне нужен доступ ко многим другим классам в родительском ClassLoader

  5. Поэтому я хочу разрешить мне переопределить родительский загрузчик классов "jar" с моим собственным: если вызываемый класс найден в загрузчике дочернего класса (например, я предоставил более новую версию Xerces со своими собственными jar-файлами) вместо того, чтобы пользователи ClassLoader загрузили мой код и файлы jar.

Вот системный код, который загружает мой код + Jars (я не могу его изменить)

File addOnFolder = new File("/addOns"); 
URL url = addOnFolder.toURL();         
URL[] urls = new URL[]{url};
ClassLoader parent = getClass().getClassLoader();
cl = URLClassLoader.newInstance(urls, parent);

Вот код "my" (полностью взят из демонстрационной версии кода "Hello World" от Flying Sauser):

package flyingsaucerpdf;

import java.io.*;
import com.lowagie.text.DocumentException;
import org.xhtmlrenderer.pdf.ITextRenderer;

public class FirstDoc {

    public static void main(String[] args) 
            throws IOException, DocumentException {

        String f = new File("sample.xhtml").getAbsolutePath();
        System.out.println(f);
        //if(true) return;
        String inputFile = "sample.html";
        String url = new File(inputFile).toURI().toURL().toString();
        String outputFile = "firstdoc.pdf";
        OutputStream os = new FileOutputStream(outputFile);

        ITextRenderer renderer = new ITextRenderer();
        renderer.setDocument(url);
        renderer.layout();
        renderer.createPDF(os);

        os.close();
    }
}

Это работает автономно (работает main), но завершается ошибкой при загрузке через родительский CL:

org.w3c.dom.DOMException: NAMESPACE_ERR: Предпринята попытка создать или изменить объект таким образом, что неверно в отношении Пространства имен.

вероятно, потому что родительская система использует Xerces более старой версии, и хотя я предоставляю правильный Jerces jar в папке / addOns, так как его классы уже загружены и используются родительской системой, она не разрешает мою собственную Код для использования моей собственной банки из-за направления делегации. Я надеюсь, что это проясняет мой вопрос, и я уверен, что он был задан до. (Возможно, я не задаю правильный вопрос)

Ответы [ 5 ]

28 голосов
/ 27 марта 2011

Сегодня ваш счастливый день, так как мне пришлось решить именно эту проблему. Я предупреждаю вас, хотя, внутренности загрузки классов - страшное место. Это заставляет меня думать, что разработчики Java никогда не предполагали, что вам может понадобиться последний загрузчик классов для родителей.

Чтобы использовать, просто предоставьте список URL-адресов, содержащих классы или файлы jar, которые будут доступны в дочернем загрузчике классов.

/**
 * A parent-last classloader that will try the child classloader first and then the parent.
 * This takes a fair bit of doing because java really prefers parent-first.
 * 
 * For those not familiar with class loading trickery, be wary
 */
private static class ParentLastURLClassLoader extends ClassLoader 
{
    private ChildURLClassLoader childClassLoader;

    /**
     * This class allows me to call findClass on a classloader
     */
    private static class FindClassClassLoader extends ClassLoader
    {
        public FindClassClassLoader(ClassLoader parent)
        {
            super(parent);
        }

        @Override
        public Class<?> findClass(String name) throws ClassNotFoundException
        {
            return super.findClass(name);
        }
    }

    /**
     * This class delegates (child then parent) for the findClass method for a URLClassLoader.
     * We need this because findClass is protected in URLClassLoader
     */
    private static class ChildURLClassLoader extends URLClassLoader
    {
        private FindClassClassLoader realParent;

        public ChildURLClassLoader( URL[] urls, FindClassClassLoader realParent )
        {
            super(urls, null);

            this.realParent = realParent;
        }

        @Override
        public Class<?> findClass(String name) throws ClassNotFoundException
        {
            try
            {
                // first try to use the URLClassLoader findClass
                return super.findClass(name);
            }
            catch( ClassNotFoundException e )
            {
                // if that fails, we ask our real parent classloader to load the class (we give up)
                return realParent.loadClass(name);
            }
        }
    }

    public ParentLastURLClassLoader(List<URL> classpath)
    {
        super(Thread.currentThread().getContextClassLoader());

        URL[] urls = classpath.toArray(new URL[classpath.size()]);

        childClassLoader = new ChildURLClassLoader( urls, new FindClassClassLoader(this.getParent()) );
    }

    @Override
    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        try
        {
            // first we try to find a class inside the child classloader
            return childClassLoader.findClass(name);
        }
        catch( ClassNotFoundException e )
        {
            // didn't find it, try the parent
            return super.loadClass(name, resolve);
        }
    }
}

РЕДАКТИРОВАТЬ : Серхио и Чоги указали, что если вы позвоните .loadClass с тем же именем класса, вы получите ошибку LinkageError. Хотя это и так, нормальный вариант использования этого загрузчика классов - установить его как загрузчик классов потока Thread.currentThread().setContextClassLoader() или через Class.forName(), и он работает как есть.

Однако, если .loadClass() требовался напрямую, этот код можно добавить в метод findClass ChildURLClassLoader вверху.

                Class<?> loaded = super.findLoadedClass(name);
                if( loaded != null )
                    return loaded;
18 голосов
/ 21 июня 2011

Следующий код - это то, что я использую. У него есть преимущество перед другим ответом, что он не разрывает родительскую цепочку (вы можете следовать getClassLoader().getParent()).

Он также имеет преимущество перед Tomcat WebappClassLoader, поскольку не изобретает колесо и не зависит от других объектов. Он максимально использует код из URLClassLoader.

(он еще не учитывает загрузчик системных классов, но когда я это исправлю, я обновлю ответ)

Он соблюдает загрузчик системных классов (для java. * Классов, утвержденных dir и т. Д.). Это также работает, когда защита включена, и загрузчик классов не имеет доступа к своему родителю (да, эта ситуация странная, но возможная).

public class ChildFirstURLClassLoader extends URLClassLoader {

    private ClassLoader system;

    public ChildFirstURLClassLoader(URL[] classpath, ClassLoader parent) {
        super(classpath, parent);
        system = getSystemClassLoader();
    }

    @Override
    protected synchronized Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            if (system != null) {
                try {
                    // checking system: jvm classes, endorsed, cmd classpath, etc.
                    c = system.loadClass(name);
                }
                catch (ClassNotFoundException ignored) {
                }
            }
            if (c == null) {
                try {
                    // checking local
                    c = findClass(name);
                } catch (ClassNotFoundException e) {
                    // checking parent
                    // This call to loadClass may eventually call findClass again, in case the parent doesn't find anything.
                    c = super.loadClass(name, resolve);
                }
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }

    @Override
    public URL getResource(String name) {
        URL url = null;
        if (system != null) {
            url = system.getResource(name); 
        }
        if (url == null) {
            url = findResource(name);
            if (url == null) {
                // This call to getResource may eventually call findResource again, in case the parent doesn't find anything.
                url = super.getResource(name);
            }
        }
        return url;
    }

    @Override
    public Enumeration<URL> getResources(String name) throws IOException {
        /**
        * Similar to super, but local resources are enumerated before parent resources
        */
        Enumeration<URL> systemUrls = null;
        if (system != null) {
            systemUrls = system.getResources(name);
        }
        Enumeration<URL> localUrls = findResources(name);
        Enumeration<URL> parentUrls = null;
        if (getParent() != null) {
            parentUrls = getParent().getResources(name);
        }
        final List<URL> urls = new ArrayList<URL>();
        if (systemUrls != null) {
            while(systemUrls.hasMoreElements()) {
                urls.add(systemUrls.nextElement());
            }
        }
        if (localUrls != null) {
            while (localUrls.hasMoreElements()) {
                urls.add(localUrls.nextElement());
            }
        }
        if (parentUrls != null) {
            while (parentUrls.hasMoreElements()) {
                urls.add(parentUrls.nextElement());
            }
        }
        return new Enumeration<URL>() {
            Iterator<URL> iter = urls.iterator();

            public boolean hasMoreElements() {
                return iter.hasNext(); 
            }
            public URL nextElement() {
                return iter.next();
            }
        };
    }

    @Override
    public InputStream getResourceAsStream(String name) {
        URL url = getResource(name);
        try {
            return url != null ? url.openStream() : null;
        } catch (IOException e) {
        }
        return null;
    }

}
12 голосов
/ 27 марта 2011

Считывая исходный код либо Jetty, либо Tomcat, оба из которых предоставляют загрузчики классов, родительских для реализации семантики веб-приложения.метод findClass в вашем классе ClassLoader.Но зачем изобретать велосипед, если вы можете его украсть?

Читая ваши различные обновления, я вижу, что вы столкнулись с некоторыми классическими проблемами с системой XML SPI.

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

Именно со всем этим безумием был изобретен OSGi, но это большая пилюля для проглатывания.1015 * Даже в веб-приложениях загрузчики классов освобождают некоторые пакеты от обработки local-first при условии, что контейнер и веб-приложение должны согласовать API-интерфейс между ними.

2 голосов
/ 27 марта 2011

(см. Внизу обновление для решения, которое я нашел)

Похоже, что AntClassLoader поддерживает родительский первый / последний (еще не тестировал)

http://svn.apache.org/repos/asf/ant/core/trunk/src/main/org/apache/tools/ant/AntClassLoader.java

Вот фрагмент

/**
 * Creates a classloader for the given project using the classpath given.
 *
 * @param parent The parent classloader to which unsatisfied loading
 *               attempts are delegated. May be <code>null</code>,
 *               in which case the classloader which loaded this
 *               class is used as the parent.
 * @param project The project to which this classloader is to belong.
 *                Must not be <code>null</code>.
 * @param classpath the classpath to use to load the classes.
 *                  May be <code>null</code>, in which case no path
 *                  elements are set up to start with.
 * @param parentFirst If <code>true</code>, indicates that the parent
 *                    classloader should be consulted  before trying to
 *                    load the a class through this loader.
 */
public AntClassLoader(
    ClassLoader parent, Project project, Path classpath, boolean parentFirst) {
    this(project, classpath);
    if (parent != null) {
        setParent(parent);
    }
    setParentFirst(parentFirst);
    addJavaLibraries();
}

Обновление:

Найдено это также, когда в качествеВ крайнем случае я начал угадывать имена классов в google (это то, что создало ChildFirstURLClassLoader), но, похоже, оно некорректно

Обновление 2:

1-й вариант (AntClassLoader)очень тесно связан с Ant (требуется контекст проекта и непросто передать ему URL[]

2-й вариант (из проекта OSGI в коде Google ) не совсем то, что нужноМне нужно было, так как он искал родительский загрузчик классов перед системным загрузчиком классов (кстати, загрузчик классов Ant делает это правильно). Проблема, как я понимаю, состоит в том, что ваш родительский загрузчик классов включает в себя jar (которого он не должен иметь) функциональности, котораяне был на JDK 1.4, но был добавлен в 1.5, это не повредитs родительский загрузчик последнего класса (модель обычного делегирования, например URLClassLoader) всегда будет сначала загружать классы JDK, но здесь дочерняя наивная реализация, кажется, раскрывает старый избыточный jar в загрузчике родительского класса, скрывая собственную реализацию JDK / JRE,

Мне еще предстоит найти сертифицированную, полностью протестированную, зрелую правильную реализацию Parent Last / Child First, которая не связана с конкретным решением (Ant, Catalina / Tomcat)

Обновление3 - Я нашел это! Я смотрел не туда,

Все, что я сделал, это добавил META-INF/services/javax.xml.transform.TransformerFactory и восстановил JDK com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl вместо старого Xalan org.apache.xalan.processor.TransformerFactoryImpl

Единственная причина, по которой я пока не «принимаю свой собственный ответ», заключается в том, что я не знаю, имеет ли подход META-INF/services такое же делегирование загрузчика классов, как и у обычных классов (например, это родитель-первый / ребенок-последний илиродитель-последний / ребенок-первый?)

0 голосов
/ 26 июля 2017

Вы можете переопределить findClass() и loadClass() для реализации дочернего загрузчика первого класса:


/**
 * Always throws {@link ClassNotFoundException}. Is called if parent class loader
 * did not find class.
 */
@Override
protected final Class findClass(String name)
        throws ClassNotFoundException
{
    throw new ClassNotFoundException();
}

@Override
protected Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)){
        /*
         * Check if we have already loaded this class.
         */
        Class c = findLoadedClass(name);

        if (c == null){
            try {
                /*
                 * We haven't previously loaded this class, try load it now
                 * from SUPER.findClass()
                 */
                c = super.findClass(name);
            }catch (ClassNotFoundException ignore){
                /*
                 * Child did not find class, try parent.
                 */
                return super.loadClass(name, resolve);
            }
        }

        if (resolve){
            resolveClass(c);
        }

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