如何在Java中创建父/子/子优先ClassLoader,或如何覆盖已在父CL中加载的旧Xerces版本? [英] How do I create a parent-last / child-first ClassLoader in Java, or How to override an old Xerces version that was already loaded in the parent CL?

查看:348
本文介绍了如何在Java中创建父/子/子优先ClassLoader,或如何覆盖已在父CL中加载的旧Xerces版本?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建一个parent-last / child-first类加载器,例如一个类加载器,它将首先在子类loder中查找类,然后只委托它的父类ClassLoader来搜索类。

I would like to create a parent-last / child-first class loader, e.g. a class loader that will look for classes in the child class loder first, and only then delegate to it's parent ClassLoader to search for classes.

澄清:

我现在知道为了获得完整的ClassLoading分离,我需要使用类似URLClassLoader的东西,因为它的父级传递null,这要归功于这个答案到我之前的问题

I know now that to get complete ClassLoading seperation I need to use something like a URLClassLoader passing null as it's parent, thanks to this answer to my previous question

然而,当前的问题来帮我解决这个问题:

However the current question comes to help me resolve this issue:


  1. 我的代码+相关jar正被加载到现有系统中,使用ClassLoader将System的ClassLoader设置为父级(URLClassLoader)

  1. My code + dependent jars are being loaded into an existing system, using a ClassLoader that sets that System's ClassLoader as it's parent (URLClassLoader)

该系统使用的某些版本的库与我需要的版本不兼容(例如,旧版本的Xerces,不允许我运行我的代码)

That System uses some libraries of a version not compatible with the one I need (e.g. older version of Xerces, that doesn't allow me to run my code)

如果单独运行我的代码运行完全正常,但如果从该ClassLoader运行它会失败

My code runs perfectly fine if runs stand alone, but it fails if runs from that ClassLoader

Howerver我确实需要访问父类ClassLoader中的许多其他类

Howerver I do need access to many other classes within the parent ClassLoader

因此我想允许我覆盖,父类加载器jarswith我自己的:如果在子类加载器中找到我调用的类(例如我提供了一个更新版本的Xerces和我自己的罐子,而不是加载我的代码和罐子的ClassLoader的一个用户。

Therefore I want to allow me to Override, the parent classloader "jars" with my own: If a class I call is found in the child class loader (e.g. I provided a newer version of Xerces with my own jars, instead of the one users by the ClassLoader that loaded my code and jars.

这是加载我的代码+ Jars的系统代码(我无法更改此代码)

Here is the System's code that loads my code + Jars (I can't change this one)

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

这是我的代码(完全取自Flying SauserHello World代码演示):

Here is "my" code (taken fully from the Flying Sauser "Hello World" code demo):

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

这是独立运行的(运行主服务器)但是失败了通过父CL加载时出错:

This works standalone (running main) but fails with this error when loaded through the parent CL:


org.w3c.dom.DOMException:NAMESPACE_ERR:尝试是

的方式创建或更改对象,这与
命名空间不一致。

org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces.

可能是因为父系统使用旧版本的Xerces,即使我在/ addOns文件夹中提供了正确的Xerces jar,因为它的类已经被父系统加载和使用,它不允许我自己的由于委托的方向,使用我自己的jar的代码。我希望这会让我的问题更加清晰,我确信之前已经被问过
。 (也许我没有问正确的问题)

probably because the parent system uses Xerces of an older version, and even though I provide the right Xerces jar in the /addOns folder, since it's classes were already loaded and used by the parent System, it doesn't allow my own code to use my own jar due to the direction of the delegation. I hope this makes my question clearer, and I'm sure it has been asked before. (Perhaps I don't ask the right question)

推荐答案

今天是你的幸运日,因为我必须解决这个问题。我警告你,班级装载的内脏是一个可怕的地方。这样做让我觉得Java的设计者从未想过你可能想拥有一个父级最后一个类加载器。

Today is your lucky day, as I had to solve this exact problem. I warn you though, the innards of class loading are a scary place. Doing this makes me think that the designers of Java never imagined that you might want to have a parent-last classloader.

使用只提供包含类或者URL的URL列表可以在子类加载器中使用jar。

To use just supply a list of URLs containing classes or jars to be available in the child classloader.

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

编辑:Sergio和ɹoƃı已经指出,如果你使用相同的类名调用 .loadClass ,你将得到一个LinkageError。虽然这是真的,但这个类加载器的正常用例是将它设置为线程的类加载器 Thread.currentThread()。setContextClassLoader()或通过 Class.forName(),并按原样运行。

EDIT: Sergio and ɹoƃı have pointed out that if you call .loadClass with the same classname, you will get a LinkageError. While this is true, the normal use-case for this classloader is to set it as the thread's classloader Thread.currentThread().setContextClassLoader() or via Class.forName(), and that works as-is.

但是,如果 .loadClass()是直接需要的,这个代码可以添加到顶部的ChildURLClassLoader findClass方法中。

However, if .loadClass() was needed directly, this code could be added in the ChildURLClassLoader findClass method at the top.

                Class<?> loaded = super.findLoadedClass(name);
                if( loaded != null )
                    return loaded;

这篇关于如何在Java中创建父/子/子优先ClassLoader,或如何覆盖已在父CL中加载的旧Xerces版本?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆