我想创建一个parent-last / child-first类加载器,例如一个类加载器,它将首先在子类loder中查找类,然后只委托它的父类ClassLoader来搜索类。
澄清:
我现在知道要获得完整的ClassLoader分离,我需要使用类似URLClassLoader的东西,因为它的父级传递null,感谢qazxsw poi到我之前的问题
但是,当前的问题来帮我解决这个问题:
这是加载我的代码+ Jars的系统代码(我无法更改此代码)
this answer
这是“我的”代码(完全取自Flying Saucer“Hello World”代码演示):
File addOnFolder = new File("/addOns");
URL url = addOnFolder.toURL();
URL[] urls = new URL[]{url};
ClassLoader parent = getClass().getClassLoader();
cl = URLClassLoader.newInstance(urls, parent);
这可以独立运行(运行main)但在通过父CL加载时失败并出现此错误:
org.w3c.dom.DOMException:NAMESPACE_ERR:尝试以对名称空间不正确的方式创建或更改对象。
可能是因为父系统使用旧版本的Xerces,即使我在/ addOns文件夹中提供了正确的Xerces jar,因为它的类已经被父系统加载和使用,它不允许我自己的代码使用由于代表团的指示,我自己的罐子。我希望这会让我的问题更加清晰,我确信之前已经提出过这个问题。 (也许我不问正确的问题)
今天是你的幸运日,因为我必须解决这个问题。我警告你,班级装载的内脏是一个可怕的地方。这样做让我觉得Java的设计者从未想过你可能想拥有一个父级最后一个类加载器。
要使用只提供包含类或jar的URL列表,以便在子类加载器中可用。
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();
}
}
编辑:Sergio和ɹoƃı指出,如果你使用相同的类名调用/**
* 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);
}
}
}
,你将得到一个LinkageError。虽然这是真的,但这个类加载器的正常用例是将它设置为线程的类加载器.loadClass
或通过Thread.currentThread().setContextClassLoader()
,并且按原样工作。
但是,如果直接需要Class.forName()
,则可以在顶部的ChildURLClassLoader findClass方法中添加此代码。
.loadClass()
我使用的是以下代码。它具有优于其他答案的优势,它不会破坏父链(您可以关注 Class<?> loaded = super.findLoadedClass(name);
if( loaded != null )
return loaded;
)。
它还具有优于tomcat的WebappClassLoader的优势,它不重新发明轮子而不依赖于其他对象。它尽可能地重用URLClassLoader中的代码。
(它还没有兑现系统类加载器,但是当我得到修复时我会更新答案)
它尊重系统类加载器(对于java。*类,背书目录等)。当安全性打开并且类加载器无法访问其父级时,它也可以工作(是的,这种情况很奇怪,但可能)。
getClassLoader().getParent()
通过读取Jetty或Tomcat的源代码,两者都提供了父类最后一个类加载器来实现webapp语义。
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;
}
}
也就是说,通过覆盖https://github.com/apache/tomcat/blob/7.0.93/java/org/apache/catalina/loader/WebappClassLoaderBase.java类中的findClass
方法。但是,当你可以偷走它时,为什么要重新发明轮子呢?
阅读各种更新,我发现您遇到了XML SPI系统的一些经典问题。
一般的问题是:如果你创建一个完全孤立的类加载器,那么很难使用它返回的对象。如果允许共享,则当父包含错误的版本时,您可能会遇到问题。
它是为了处理OSGi发明的所有这些疯狂,但这是一个吞下的大药丸。
即使在webapps中,类加载器也会在假设容器和webapp必须就它们之间的API达成一致的情况下从“本地优先”处理中免除一些包。
(请参阅底部,了解我发现的解决方案的更新)
似乎AntClassLoader支持父第一个/最后一个,(还没有测试它)
ClassLoader
这是一个片段
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();
}
,作为最后的手段我开始在谷歌猜测类名(这是ChildFirstURLClassLoader制作的) - 但它似乎是不正确的
更新2:
第一个选项(AntClassLoader)与Ant非常耦合(需要一个项目上下文,并不容易将this传递给它
第二个选项(来自URL[]
)并不是我所需要的,因为它在系统类加载器之前搜索父类加载器(Ant类加载器正确地执行它)。我看到的问题是,你认为你的父类加载器包含一个不在JDK 1.4但在1.5中添加的功能的jar(它应该没有),这对于父类最后一个类加载器没有任何害处(常规委托模型,例如URLClassLoader)将始终首先加载JDK的类,但是这里的第一个天真实现似乎揭示了父类加载器中的旧的冗余jar,影响了JDK / JRE自己的实现。
我还没有找到一个经过认证,经过全面测试,成熟的Parent Last / Child First正确的实现,它没有与特定的解决方案相结合(Ant,Catalina / Tomcat)
更新3 - 我找到了!我在找错了地方,
我所做的就是添加OSGI project in google code并恢复JDK的META-INF/services/javax.xml.transform.TransformerFactory
而不是旧的Xalan的com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
我还没有“接受我自己的答案”的唯一原因是,我不知道org.apache.xalan.processor.TransformerFactoryImpl
方法是否具有与常规类相同的类加载器委托(例如,它是父级优先/子级最后还是父级最后/孩子一?)
您可以覆盖META-INF/services
和findClass()
来实现子类第一类加载器:
loadClass()