Tomcat类加载问题(一)
最近有个同事在公司碰到了一个关于类加载的问题拉我一起看了一下,研究后发现挺经典的,写篇笔记记录一下。
问题现象
这个应用是一个框架型的应用,提供了一种插件机制,可以通过注册jar包插件进行一些定制化的数据分析入库操作。运行背景:
1、定制了插件A,jar包为xxx_plugin.jar
2、插件A中调用了CBB的jar包,xxxx_cbb.jar
3、web应用创建任务过程中也同样调用了xxxx_cbb.jar
4、任务由其他应用App_n调用此应用的接口,接口通过识别对应的字段选取插件创建任务通过反射获取插件运行实例放到队列中,插件运行实例继承与接口Interface_C,放到队列时会强制转换成接口Interface_C。
问题现象:
在插件A中初始化了实例Impl_B,此实例继承了接口Interface_C,在返回任务实例的时候把Impl_B强制转换成Interface_C实例返回,但是在强制转换时抛出了ClassCastException。
问题分析
通过调试发现,代码中获取的实例是对应正确的类的实例,转换的接口C也的确有实现关系,但是强转的时候抛异常,那只能说明插件中加载的Interface_C与强制转换的目标Interface_C是由不同的类加载器加载的,所以并不是同一个Interface_C,强制转换失败。
我翻了一下代码目录,当前应用目录如下:
${catalina_base}
-lib
-xxx_cbb.jar
-plugins
-xxx_plugin.jar
-webapps
-ROOT
-WEB-INF
-lib
-xxx_cbb.jar
其中:${catalina_base}/lib为tomcat的common.loader目录
可以发现,xxx_cbb.jar在webapp目录的lib下,与common.loader的目录下均存在,很有可能就是这个原因导致的。
问题重现
之前记得tomcat的类加载是比较特殊的,webapp的类加载是不遵勖双亲委派原则的,查资料看了一下,大概知道问题所在。
当应用需要到某个类时,则会按照下面的顺序进行类加载:
1 使用bootstrap引导类加载器加载
2 使用system系统类加载器加载
3 使用应用类加载器在WEB-INF/classes中加载
4 使用应用类加载器在WEB-INF/lib中加载
5 使用common类加载器在CATALINA_HOME/lib中加载
因此,如果插件的Interface_C是由SystemClassLoader加载,而webApp的Interface_C是由WebAppClassLoader加载,那么进行强制转化时必然报错。
根据这个分析,模拟了一下问题:
文件目录:
代码:
import package1.Interface1;
import java.io.File;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
Thread.currentThread().setContextClassLoader(myPluginClassLoader(Thread.currentThread().getContextClassLoader()));
ClassLoader myWebappClassLoader = myWebappLoader()