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

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

пояснение:

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

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

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

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

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

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

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

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

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

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.дом.DOMException: NAMESPACE_ERR: попытка для создание или изменение объекта что неверно в отношении пространство имен.

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

5 ответов


Сегодня ваш счастливый день, так как я должен был решить эту точную проблему. Я предупреждаю вас, что внутренности загрузки класса-страшное место. Это заставляет меня думать, что дизайнеры 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);
        }
    }
}

редактировать: Серджио иooıı указали, что если вы позвоните .loadClass с тем же именем класса вы получите LinkageError. Хотя это верно, обычный вариант использования для этого загрузчика классов-установить его как classloader потока Thread.currentThread().setContextClassLoader() или через Class.forName(), и это работает как есть.

, если .loadClass() был необходим напрямую, этот код может быть добавлен в метод Findclass ChildURLClassLoader вверху.
                Class<?> loaded = super.findLoadedClass(name);
                if( loaded != null )
                    return loaded;

следующий код-это то, что я использую. У него есть преимущество перед другим ответом, что он не нарушает родительскую цепочку (вы можете следовать 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;
    }

}

читая исходный код 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.

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

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


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

кажется, что AntClassLoader поддерживает Родительский first / last, (еще не тестировал его)

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 делает это правильно путь.) Проблема, как я понимаю, считаете, что ваш родительский загрузчик классов включает в себя баночку (что это не стоило) на функциональность это не на JDK 1.4, но был добавлен в 1.5, это не имеет никакой вред как родитель последней загрузчик класса (регулярный делегация модели, например urlclassloader, на) всегда сначала загрузить JDK и классы, но здесь ребенок сначала наивная реализация, кажется, чтобы раскрыть старые, резервные банки в родительский загрузчик Class, слежка за комплект JDK / JRE и собственную реализацию.

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

обновление 3 - я нашел его! Я искал не в том месте,

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

единственная причина, по которой я еще не "принимаю свой собственный ответ", заключается в том, что я не знаю, если META-INF/services подход имеет то же самое делегирование classloader как обычные классы (например, это parent-first / child-last или parent-last / child-first?)


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