我想要创建一个父类-最后/子-第一类加载器,例如一个类加载器,它将首先在子类loder中查找类,然后委托它的父ClassLoader搜索类。
Clarification:
现在我知道了,为了获得完整的ClassLoading分离,我需要使用类似于URLClassLoader传递null作为它的父级的东西,这要归功于前面的问题中的这个答案
然而,目前的问题来帮助我解决这个问题:
以下是加载我的代码+ Jars的系统代码(我无法更改此代码)
File addOnFolder = new File("/addOns");
URL url = addOnFolder.toURL();
URL[] urls = new URL[]{url};
ClassLoader parent = getClass().getClassLoader();
cl = URLClassLoader.newInstance(urls, parent);
下面是“我的”代码(完全摘自"Hello“代码演示):
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。我希望这使我的问题更清楚,我相信这是以前的问题。(也许我没有问对的问题)
发布于 2011-03-27 01:45:34
今天是你的幸运日,因为我必须解决这个问题。不过,我警告你,课堂装填的内部是个可怕的地方。这样做让我觉得Java的设计者从来没有想过你可能想要一个父类-最后一个类加载器。
要使用,只需提供一个URL列表,其中包含要在子类加载器中可用的类或jars。
/**
* 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方法中。
Class<?> loaded = super.findLoadedClass(name);
if( loaded != null )
return loaded;
发布于 2011-03-26 22:38:36
通过阅读Jetty或Tomcat的源代码,它们都提供了父类加载器来实现webapp语义。
也就是说,通过重写findClass
类中的ClassLoader
方法。但既然你能偷,为什么要重新发明轮子呢?
通过阅读您的各种更新,我看到您在XML系统中遇到了一些典型的问题。
一般的问题是:如果您创建了一个完全隔离的类加载器,那么很难使用它返回的对象。如果允许共享,则当父版本包含错误的内容时,可能会出现问题。
正是为了应对所有这些疯狂,OSGi发明了,但这是一个大药丸吞咽。
即使在webapp中,类加载器也可以豁免一些包不受“本地优先”处理的影响,前提是容器和webapp必须就它们之间的API达成一致。
发布于 2011-03-27 02:35:02
(有关我找到的解决方案的更新,请参见底部)
AntClassLoader似乎支持父母第一/最后(还没有测试)
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:
第一个选项(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
方法是否与普通类具有相同的类加载器委托(例如,它是父类优先/子类还是父类/最后/子类?)
https://stackoverflow.com/questions/5445511
复制相似问题