首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何在Java中创建父-最后/子-第一ClassLoader,或者如何覆盖已经加载在父CL中的旧Xerces版本?

如何在Java中创建父-最后/子-第一ClassLoader,或者如何覆盖已经加载在父CL中的旧Xerces版本?
EN

Stack Overflow用户
提问于 2011-03-26 21:40:52
回答 5查看 39.3K关注 0票数 38

我想要创建一个父类-最后/子-第一类加载器,例如一个类加载器,它将首先在子类loder中查找类,然后委托它的父ClassLoader搜索类。

Clarification:

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

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

  1. 我的代码+依赖的jars正在加载到现有的系统中,使用ClassLoader将系统的ClassLoader设置为父系统(URLClassLoader)
  2. 该系统使用了一些与我需要的版本不兼容的库(例如,旧版本的Xerces,这不允许我运行我的代码)
  3. 如果独立运行,我的代码运行非常好,但是如果从那个ClassLoader运行它就会失败。
  4. 但是,我确实需要访问父ClassLoader中的许多其他类。
  5. 因此,我希望允许我重写父类加载器" jars“:如果在子类加载器中找到了我调用的类(例如,我用自己的jars提供了Xerces的更新版本,而不是加载代码和jars的classloader用户)。

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

代码语言:javascript
运行
复制
File addOnFolder = new File("/addOns"); 
URL url = addOnFolder.toURL();         
URL[] urls = new URL[]{url};
ClassLoader parent = getClass().getClassLoader();
cl = URLClassLoader.newInstance(urls, parent);

下面是“我的”代码(完全摘自"Hello“代码演示):

代码语言:javascript
运行
复制
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,而且即使我在/addOns文件夹中提供了正确的Xerces jar,因为父系统已经加载和使用了它的类,因此由于委托的方向,它不允许我自己的代码使用自己的jar。我希望这使我的问题更清楚,我相信这是以前的问题。(也许我没有问对的问题)

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2011-03-27 01:45:34

今天是你的幸运日,因为我必须解决这个问题。不过,我警告你,课堂装填的内部是个可怕的地方。这样做让我觉得Java的设计者从来没有想过你可能想要一个父类-最后一个类加载器。

要使用,只需提供一个URL列表,其中包含要在子类加载器中可用的类或jars。

代码语言:javascript
运行
复制
/**
 * 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()设置,这是按原样工作的。

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

代码语言:javascript
运行
复制
                Class<?> loaded = super.findLoadedClass(name);
                if( loaded != null )
                    return loaded;
票数 35
EN

Stack Overflow用户

发布于 2011-03-26 22:38:36

通过阅读Jetty或Tomcat的源代码,它们都提供了父类加载器来实现webapp语义。

https://github.com/apache/tomcat/blob/7.0.93/java/org/apache/catalina/loader/WebappClassLoaderBase.java

也就是说,通过重写findClass类中的ClassLoader方法。但既然你能偷,为什么要重新发明轮子呢?

通过阅读您的各种更新,我看到您在XML系统中遇到了一些典型的问题。

一般的问题是:如果您创建了一个完全隔离的类加载器,那么很难使用它返回的对象。如果允许共享,则当父版本包含错误的内容时,可能会出现问题。

正是为了应对所有这些疯狂,OSGi发明了,但这是一个大药丸吞咽。

即使在webapp中,类加载器也可以豁免一些包不受“本地优先”处理的影响,前提是容器和webapp必须就它们之间的API达成一致。

票数 14
EN

Stack Overflow用户

发布于 2011-03-27 02:35:02

(有关我找到的解决方案的更新,请参见底部)

AntClassLoader似乎支持父母第一/最后(还没有测试)

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

这里有一个片段

代码语言:javascript
运行
复制
/**
 * 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:

第一个选项(AntClassLoader)非常耦合到Ant (需要一个项目上下文,并且不容易将一个URL[]传递给它)

第二个选项(来自google代码中的OSGI项目)不是我所需要的,因为它在系统类加载器之前搜索了父类加载器(顺便提一句,Ant类加载器正确地完成了)。在我看来,问题在于,您的父类加载器包含一个jar (它不应该包含),它的一个功能不是在JDK1.4上添加的,而是在1.5中添加的,因为父类加载器(常规委托模型,例如URLClassLoader)总是首先加载JDK的类,但在这里,子类first朴素实现似乎在父类加载器中公开了旧的冗余jar,从而隐藏了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方法是否与普通类具有相同的类加载器委托(例如,它是父类优先/子类还是父类/最后/子类?)

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/5445511

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档