前言
写这篇文章的目的主要是在分析shiro反序列化的时候遇到了tomcat加载不了transform数组的问题,于是把tomcat涉及到的几个类加载器都研究了一下,写个文章总结一下,如有问题欢迎师傅指出。
Tomcat为什么要实现这么多类加载器?
Tomcat是个容器,一个web容器可能需要部署两个应用程序,不同的应用会依赖不同的第三方库的不同版本,因此要求有些类库需要隔离,有些类库需要共享。
Tomcat类加载器
按照设计图并结合源码来分析:
Common类加载器
Tomcat最基本的类加载器,默认是加载$CATALINE_HOME/lib 下的jar 包
我来看看源码:
首先commonLoader
在Bootstrap
中初始化,调用createClassLoader
来创建,之后会作为catalinaLoader
和sharedLoader
的父加载器
其中会调用CatalinaProperties.getProperty
获取要加载的路径,property对应配置文件catalina.properties
或虚拟启动时参数赋值,之后就是些扫描path中的jar包和类。
catalina类加载器
Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见,听名字像是Catalina类的加载器,但不完全是
我来看看源码:
在Bootstrap
中初始化,调用createClassLoader
来创建,以commonLoader
作为父加载器
调用CatalinaProperties.getProperty
,默认为空,所以直接使用父加载器
之后用catalinaLoader
去加载并实例化org.apache.catalina.startup.Catalina
之后又通过反射将ParentClassLoader
改成了sharedLoader
所以调用WepAppClassLoader
来加载时使用的parent
其实是sharedLoader
。
shared类加载器
各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见
来看看源码:
在Bootstrap
中初始化,调用createClassLoader
来创建,以commonLoader
作为父加载器
调用CatalinaProperties.getProperty
,默认为空,所以直接使用父加载器
之后就是通过反射将Catalina.parentClassLoader
设置为sharedLoader
Webapp类加载器
各个Webapp私有的类加载器,加载我们web应用中,"WEB-INF/libs" 和"WEB-INF/classes" 这个目录下的jar
来看看源码:
初始化在StandardContext.startInternal
,WebAppClassLoader
可以使用SharedClassLoader
加载到的类,但各个WebAppClassLoader
实例之间相互隔离
具体加载器是通过实例化WebappClassLoaderBase
JasperLoader
每个jsp文件都由一个JasperLoader去加载,JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个Class,它出现的目的就是为了被丢弃:当服务器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的热部署功能。
这里不详细的去研究
tomcat的类加载机制
我们知道,tomcat的类加载机制是违反了双亲委托原则的,具体是怎么实现的
直接看源码:
来到WebappClassLoaderBase#loadClass(java.lang.String, boolean)
,首先调用findLoadedClass0
,先从缓存中获取有无加载过的class
判断有无开启双亲委派机制,有的话会先委派父加载器去加载
使用WebApp类加载器去加载,其逻辑是扫描"WEB-INF/libs"和"WEB-INF/classes"目录下jar包中有无对应name的class类,有的话就返回,具体扫描的逻辑是用jarFile
类实现的,有兴趣的师傅可以去跟下。
这时我们的[Lorg.apache.commons.collections.Transformer;
这个类名是肯定扫描不到的,而org.apache.commons.collections.Transformer
类名是可以扫描到的
后面判断没有开启双亲委派的话,会用commonloader
去加载类,也就是$CATALINE_HOME/lib
目录下的。
[Lorg.apache.commons.collections.Transformer;
肯定是加载不到的,而[Ljava.lang.StackTraceElement;
就可以加载到
最后所有加载器都加载不到的话,就抛出ClassNotFoundException
的异常
所以网上很多说加载不了数组的结论是错的,准确来说可以加载共享库中的对象数组但不能加载自定义类和第三方类的对象数组
总结
tomcat的类加载机制是违反了双亲委托原则的,对于一些未加载的非基础类(Object,String等),各个web应用自己的类加载器(WebAppClassLoader)会优先加载,加载不到时再交给commonClassLoader走双亲委托。所以也不会导致核心api被篡改的问题。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)