如何在 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?

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

问题描述

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

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. 我的代码 + 依赖 jars 正在加载到现有系统中,使用类加载器将该系统的类加载器设置为它的父级 (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)

如果单独运行,我的代码运行得非常好,但如果从该类加载器运行,则会失败

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

但是我确实需要访问父类加载器中的许多其他类

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

因此,我想允许我覆盖父类加载器jars"与我自己的:如果在子类加载器中找到我调用的类(例如,我提供了带有我自己的 jars 的更新版本的 Xerces,而不是加载我的代码和 jar 的 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.

这是加载我的代码 + 罐子的系统代码(我不能改变这个)

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 Sauser 的Hello 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 的设计者从来没有想过你可能想要一个 parent-last 类加载器.

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.

仅使用提供包含在子类加载器中可用的类或 jar 的 URL 列表.

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

EDIT: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天全站免登陆