如何在Java中创建父last/child first类加载器,或者如何覆盖已加载到父CL中的旧Xerces版本?
我想创建一个父类最后/子类第一类加载器,例如,一个类加载器将首先在子类loder中查找类,然后才委托给它的父类加载器来搜索类 澄清: 我现在知道,要获得完整的类加载分离,我需要使用类似URLClassLoader的东西,将null作为其父类传递,这要感谢我前面的问题 但是,当前的问题有助于我解决这个问题:如何在Java中创建父last/child first类加载器,或者如何覆盖已加载到父CL中的旧Xerces版本?,java,classloader,Java,Classloader,我想创建一个父类最后/子类第一类加载器,例如,一个类加载器将首先在子类loder中查找类,然后才委托给它的父类加载器来搜索类 澄清: 我现在知道,要获得完整的类加载分离,我需要使用类似URLClassLoader的东西,将null作为其父类传递,这要感谢我前面的问题 但是,当前的问题有助于我解决这个问题: 我的代码+依赖JAR正在加载到现有系统中,使用一个类加载器将该系统的类加载器设置为其父类(URLClassLoader) 该系统使用的某些库的版本与我需要的版本不兼容(例如旧版本的Xerces
之前。(也许我问的问题不对)通过阅读Jetty或Tomcat的源代码,这两个工具都提供了实现webapp语义的父类最后类装入器 也就是说,通过覆盖
ClassLoader
类中的findClass
方法。但是,既然可以偷,为什么还要重新发明轮子呢
阅读您的各种更新,我发现您遇到了一些XML SPI系统的经典问题
一般的问题是:如果创建一个完全隔离的类加载器,则很难使用它返回的对象。如果允许共享,则当父类包含错误版本的内容时,可能会出现问题
OSGi的发明是为了应对所有这些疯狂行为,但这是一个需要吞下的大药丸
即使在webapps中,类加载器也会免除某些包的“本地优先”处理,前提是容器和webapp必须就它们之间的API达成一致。今天是你的幸运日,因为我必须解决这个确切的问题。不过,我警告你,类加载的内部是一个可怕的地方。这样做让我觉得Java的设计者从来没有想到,您可能想要一个父类装入器
/**
* 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);
}
}
}
要使用,只需提供一个URL列表,其中包含子类加载器中可用的类或JAR
/**
* 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);
}
}
}
(有关我找到的解决方案的更新,请参见底部)
AntClassLoader似乎支持父first/last(尚未测试)
这里是一个片段
/**
* 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非常耦合(需要一个项目上下文,并且不容易向其传递URL[]
)
第二个选项(来自一个)不是我所需要的,因为它在系统类加载器之前搜索父类加载器(顺便说一句,Ant类加载器做得很正确)对于JDK1.4中没有但在1.5中添加的功能,作为父类最后一个类加载器(常规委托模型,例如URLClassLoader),这没有什么害处将始终首先加载JDK的类,但在这里,child first naive实现似乎揭示了父类加载程序中旧的冗余jar,隐藏了JDK/JRE自己的实现
我还没有找到一个经过认证的、经过充分测试的、成熟的父级Last/Child First正确实现,它没有与特定的解决方案(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
方法是否具有与常规类相同的类加载器委托(例如,是父级优先/子级最后还是父级最后/子级优先?)下面的代码就是我使用的代码。与其他答案相比,它的优点是不会破坏父链(您可以遵循getClassLoader().getParent()
)
与tomcat的WebappClassLoader相比,它还有一个优势,即不需要重新发明轮子,也不依赖于其他对象
Class<?> loaded = super.findLoadedClass(name);
if( loaded != null )
return loaded;
/**
* 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();
}
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;
}
}
/**
* 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;
}
}