自定义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);
}
}