tomcat类加载器及jar包冲突问题分析

tomcat类加载器及jar包冲突问题分析

开发过程中遇到过这样一个情况,在本地tomcat下开发调试正常,打包到测试环境的jboss下所有页面都变成空白页。
项目日志和jboss日志没有一点异常信息,费了半天劲把jboss所有日志全部打出来,发现是el.jar这个包里有空指针调用。
检查一下,项目WEB-INF\lib里有这个包呀,那应该是跟什么地方的jar包版本冲突了猜想,继续找,在jboss-4.0.5.GA\server\default\lib下找到了对应包,比较了一下版本果然版本不一样。
把项目下的el-api.jar,jsp-api.jar,servlet-api.jar删除,重新启动,问题解决。
接着有同事提出,同样在tomcat下开发也出现这种情况,经检查是他本地tomcat版本跟大家的不一致。开发环境这地方没做到很好的统一。

这样问题是解决了,但是有一点就想不明白了,按照java的类加载委托机制,推测应该是先从jboss-4.0.5.GA\server\default\lib加载,如果加载不到的话再用当前类加载器加载WEB-INF\lib下的jar包,所有如果jboss下有jar包WEB-INF\lib下的应该不起作用,也有不会有冲突了。
难到情况不是这样的?

一直想着找tomcat源码分析一下来着,拖了好久。赶上这两天不忙,就把apache-tomcat-6.0.33-src源文件弄了一份,debug看看到底怎样。
tomcat的类加载器结构和其他java项目是一致的。见图一

tomcat类加载器及jar包冲突问题分析_第1张图片
图一
其类图见图二
tomcat类加载器及jar包冲突问题分析_第2张图片
图二
elipse debug截图倒过来看跟这个就一样了。
Tomcat 通过Lifecycle接口来实现容器生命周期的统一管理,跟类加载器关系不大,这里就不讨论了。
通过这样大容器启动的时候启动子容器,逐级加载。其结构关系跟server.xml描述的基本一致,详细可以参考我的上一篇文章

Tomcat6结构分析
http://www.blogjava.net/zyskm/archive/2011/10/24/361870.html

(这个编辑工具不太会用,样式难看点,凑合看了)
每个容器都有自己的类加载器,在默认情况下都是StandardClassLoader的实例。
委托机制也和标准的java实现没什么两样。

接着往下看项目对应的类加载
StandardContext.start();调用WebappLoader.start()开始加载项目,WebappLoader又通过创建一个WebappClassLoader实例进行类加载。
WebappClassLoader.loadClass()实现依然波澜不惊,规规矩矩的先从缓存找,找不到调用findClass()进行加载。
果然这里实现有点不同,是先自己找,找不到再委托上级查找。和java默认的加载方式不同。
见源代码,只留下原理部分,日志和调试信息都去掉了。

public  Class findClass(String name)  throws  ClassNotFoundException  {
        
// 先自己加载类,找不到则请求parent来加载,注意这点和java默认的委托模式不同
        Class clazz = null;
        
try {
            
if ((clazz == null)) {
                    clazz 
= findClassInternal(name);
            }

            
if ((clazz == null&& hasExternalRepositories && !searchExternalFirst) {
                    clazz 
= super.findClass(name);
            }

            
if (clazz == null{
                
throw new ClassNotFoundException(name);
            }

        }
 catch (ClassNotFoundException e) {
            
if (log.isTraceEnabled())
                log.trace(
"    --> Passing on ClassNotFoundException");
            
throw e;
        }

        
return (clazz);
}

据此可以认为,在web项目WEB-INF\lib下的jar包优先级高于jboss,tomcat 下的lib.
两处版本不一致的话会导致程序异常。
比较省事的办法是WEB-INF\lib下不再保留重复的jar包,实在闲着没事的话可以自己写个类加载器替换tomcat下WebappClassLoader改变加载顺序。
但是还可能有隐患,WebappClassLoader权限较低,它加载的类只能访问web应用下的资源,如果servlet-api.jar等包用到其他资源时可能出现异常。
这个没实际测过,只是推测。但是catalina要提供对整个容器的支持,servlet-api实现对http协议的封装转换用到外部资源的可能性很大。

tomcat类加载器及jar包冲突问题分析_第3张图片
图三 类加载器结构图

总结:
sevlet-api.jar,jsp-api.jar,el-api.jar这类容器提供的jar包web项目下没必要再保留一份了,容易出现版本不一致。

附录:
查看tomcat源码的时候可以看看how tomcat works这本书,很不错,虽然老了点。

作者:zyskm
http://www.blogjava.net/zyskm

你可能感兴趣的:(tomcat类加载器及jar包冲突问题分析)