自定义ClassLoader相信很多人都用过,网上文章也有很多。但如何使用自定义的ClassLoader有时确实比较头痛。

如果启动入口自己可以控制还好说,大不了通过自定义ClassLoader加载所有类就可以了,但如果控制不了,比如说是通过TOMCAT或脚本来启动的,但又要用自定义的ClassLoader来加载外部类,那就郁闷了。

我碰到的情形就是如此,其中的ClassLoaderC是tomcat的类加载器,而classLoaderD是自定义的类加载器。通常来说,我们只能选择访问C或D其中一个下面的类。有没办法能同时访问它们下两个的类呢?

其中一种办法是Thread.currentThread().setContextClassLoader。相对比较方便,但这在多线程环境下很容易产生问题。

还有一种办法是通过反射调用,修改ClassLoaderC的parent为ClassLoaderD。我们知道ClassLoader的委托机制是先让parent(父)类加载器寻找,只有在parent找不到的时候才从自己的类路径中去寻找。这样我们通过修改parent就能达到同时访问的目的。当然,由于parent是私有的,而且没有提供写方法,所以还需要用反射来设置。

之前还尝试了另一种方法,即ClassLoader.addClass,但发现类是进去了,但package里没有,还是会加载不到类。

这种方法目前还在试用,大家觉得有什么问题欢迎提出来:)

public class ContainerClassLoader extends ClassLoader {  
  
    private Map<String, Class<?>> loadedClasses = new HashMap<String, Class<?>>();  
  
    private static ContainerClassLoader INSTANCE;  
  
    private ContainerClassLoader() {  
        super(ContainerClassLoader.class.getClassLoader().getParent());  
    }  
  
    /** 
     * 初始化 
     */  
    public static void init() {  
        INSTANCE = new ContainerClassLoader();  
        try {  
            INSTANCE.addThisToParentClassLoader(ContainerClassLoader.class  
                    .getClassLoader());  
        } catch (Exception e) {  
            System.err.println("设置classloader到容器中时出现错误!");  
        }  
    }  
  
    @SuppressWarnings({ "unchecked", "rawtypes" })  
    @Override  
    public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {  
        if (loadedClasses.containsKey(name)) {  
            return loadedClasses.get(name);  
        }  
        return super.loadClass(name, resolve);  
    }  
  
    /** 
     * 将this替换为指定classLoader的parent ClassLoader 
     *  
     * @param classLoader 
     */  
    private void addThisToParentClassLoader(ClassLoader classLoader) throws Exception {  
        Field field = ClassLoader.class.getDeclaredField("parent");  
        field.setAccessible(true);  
        field.set(classLoader, this);  
    }  
  
}

另外,在过程中还碰到spring加载时,classloader还没修改的问题。后来通过在web.xml中增加listener-class来实现。

后面还碰到了问题,如果将TOMCAT的当前ClassLoader的parent修改了,还是会碰到类无法识别的问题。 
原因是WebappClassLoader类也有个私有属性,与基类ClassLoader名称一样,而且是在WebappClassLoader类构造时赋值。 
处理方法:通过反射将WebappClassLoader对象的两个私有属性都修改。 如下: 

private void addThisToParentClassLoader(ClassLoader classLoader) throws Exception {  
       Field field;  
       try {  
           //将当前ClassLoader的parent属性修改为本对象(适用于WebClassLoader)  
           field = classLoader.getClass().getDeclaredField("parent");  
           field.setAccessible(true);  
           field.set(classLoader, this);  
       } catch (Exception e) {  
       }  
       //将当前ClassLoader的parent ClassLoader修改为本对象  
       field = ClassLoader.class.getDeclaredField("parent");  
       field.setAccessible(true);  
       field.set(classLoader, this);  
}