Подтвердить что ты не робот

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

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

Разъяснение:

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

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

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

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

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

  • Howerver Мне нужен доступ ко многим другим классам в родительском ClassLoader

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

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

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

Вот "мой" код (полностью взятый из демоверсии "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 более старой версии, и даже если я предоставляю правильную jerer Xerces в папке /addOns, так как она уже загружалась и использовалась родительской системой, она не позволяет мне код для использования моей собственной банки из-за направления делегации. Надеюсь, этот вопрос станет более ясным, и я уверен, что его спросили до. (Возможно, я не задаю правильный вопрос)

4b9b3361

Ответ 1

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

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

/**
 * 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;

Ответ 2

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

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

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

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

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;
    }

}

Ответ 3

Прочитав исходный код Jetty или Tomcat, оба из которых предоставляют родительские-классные загрузчики классов для реализации семантики webapp.

http://svn.apache.org/repos/asf/tomcat/tc7.0.x/tags/TOMCAT_7_0_0/java/org/apache/catalina/loader/WebappClassLoader.java

То есть, переопределяя метод findClass в вашем классе ClassLoader. Но зачем изобретать колесо, когда вы можете его украсть?

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

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

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

Даже в webapps загрузчики классов освобождают некоторые пакеты от обработки "local-first" в предположении, что контейнер и webapp должны согласовать API между ними.

Ответ 4

(см. внизу обновление для найденного решения)

Кажется, что 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();
}

Update:

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

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

Первый вариант (AntClassLoader) очень привязан к Ant (требуется контекст Project и не легко передать ему URL[]

Второй вариант (из проекта OSGI в коде google) не совсем то, что мне нужно, поскольку он искал родительский загрузчик классов перед загрузкой class classloader (Ant загрузчик классов делает это правильно, кстати). Проблема, как я ее вижу, думаю, что ваш родительский загрузчик классов включает в себя банку (которой она не должна быть) функциональности, которая не была на JDK 1.4, но была добавлена ​​в 1.5, это не имеет вреда как родительский последний загрузчик класса ( регулярная модель делегирования, например URLClassLoader) всегда будет загружать сначала классы JDK, но здесь первая наивная реализация ребенка, кажется, раскрывает старый избыточный баннер в загрузчике родительского класса, затеняя собственную реализацию JDK/JRE.

Мне еще предстоит найти сертифицированную, полностью проверенную, зрелую родительскую последнюю/детскую первую правильную реализацию, которая не связана с конкретным решением (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 такое же делегирование загрузчика классов, что и обычные классы (например, это parent-first/child-last или parent-last/child-first?)

Ответ 5

Вы можете переопределить 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;
    }
}