如何在Java中创build父代最后/以子代为先的ClassLoader,或者如何覆盖已经在父代CL中加载的旧Xerces版本?

我想创build一个父 – 最后/孩子优先的类加载器,例如一个类加载器将首先在子类loder中寻找类,然后委托给它的父类ClassLoader来search类。

澄清:

我现在知道,要获得完整的ClassLoading分离,我需要使用像URLClassLoader传递null作为它的父母感谢这个答案我上一个问题

但目前的问题来帮助我解决这个问题:

  1. 我的代码+从属的jar被加载到一个现有的系统中,使用一个ClassLoader来设置System的ClassLoader,因为它的父类(URLClassLoader)

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

  3. 如果运行独立,我的代码运行得很好,但是如果从ClassLoader运行,则代码将失败

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

  5. 因此,我想允许我用我自己覆盖父类加载器“jars”:如果在子类加载器中find我调用的类(例如,我用自己的jar提供了更新版本的Xerces,而不是一个用户由加载我的代码和jar子的ClassLoader。

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

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

这个工作是独立的(主要运行),但通过父CL加载时会失败,并出现此错误:

org.w3c.dom.DOMException:NAMESPACE_ERR:试图以对名称空间不正确的方式创build或更改对象。

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

今天是你的幸运日,因为我必须解决这个确切的问题。 不过,我警告你,上课的内脏是一个可怕的地方。 这样做使我觉得Java的devise者从来没有想到你可能想要一个父 – 最后一个类加载器。

要使用只提供一个包含类或jar的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); } } } 

编辑 :塞尔吉奥和ɹoƃı指出,如果你使用相同的类名调用.loadClass ,你会得到一个LinkageError。 虽然这是真的,但是这个类加载器的正常使用情况是将其设置为线程的类加载器Thread.currentThread().setContextClassLoader()或通过Class.forName() ,并且按原样工作。

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

  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

也就是说,通过覆盖ClassLoader类中的findClass方法。 但为什么重新发明轮子,当你可以偷走它?

阅读您的各种更新,我发现您遇到了XML SPI系统的一些经典问题。

一般的问题是:如果你创build一个完全隔离的类加载器,那么很难使用它返回的对象。 如果你允许共享,当父母包含错误版本的东西的时候你可能会遇到问题。

OSGi就是为了解决这个问题而发明的,但是这是一个很大的麻烦。

即使在web应用程序中,类装载器也可以免除“本地优先”处理中的一些包,前提是容器和Web应用程序必须同意它们之间的API。

(请参阅底部的关于我find的解决scheme的更新)

看起来,AntClassLoader对父进程的支持是第一个/最后一个,(还没有testing)

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

更新:

find这个 ,当作为最后的手段,我开始猜测在谷歌的类名(这是ChildFirstURLClassLoader产生的) – 但它似乎是不正确的

更新2:

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

第二个选项(从谷歌代码的OSGI项目 )是不是我所需要的,因为它在系统类加载器(Ant类加载程序正确的方式)之前search父类加载器。 问题就像我看到的那样,认为你的父类加载器包含一个不在JDK 1.4中的function的jar(它不应该有),但是在1.5中添加了这个function,常规的委托模型,例如URLClassLoader)将始终首先加载JDK的类,但是这里的孩子第一个天真的实现似乎揭示了父类加载器中的旧的冗余jar,它隐藏了JDK / JRE自己的实现。

我还没有find一个authentication的,经过全面testing,成熟的父母最后/孩子第一个正确的实施,没有耦合到一个特定的解决scheme(Ant,Catalina / Tomcat)

更新3 – 我find了! 我看错了地方,

我所做的只是添加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方法是否具有与常规类相同的类加载器委派(例如,它是父 – 先/后 – 还是父母最后/孩子第一?)

你可以重写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; } }