蹲厕所的熊 转载请注明原创出处,谢谢!
前言
相信有一定基础的同学对类加载器(ClassLoader)以及类加载机制不会陌生,如果你还不了解什么是类加载器,双亲委派模型是什么的话,先
戳我
去学习~
在看JDK、Tomcat以及Spring源码的时候,会经常的出来 这句代码,一开始我也似懂非懂的理解,但是还是不知道它的使用场景是什么,总结起来有以下几个问题:
Thread.getContextClassLoader()获取到的classLoader和getClass().getClassLoader()的有什么区别?
什么场景下会用到Thread.getContextClassLoader()的方式获取classLoader?
下面我们会从JDK的SPI机制、Tomcat以及Spring三个方面去剖析。
SPI加载问题
之前我写过一篇SPI机制的文章,其中ServiceLoader的load方法是获取到了TCCL来加载用户的类的,那么有同学想过是为什么吗?
Java提供了很多SPI,允许第三方为这些接口提供实现,最常见的SPI实现有JDBC、JNDI等等,根据类加载器的双亲委派模型,加载ServiceLoader的 是不能加载SPI的实现类的,因为SPI的实现类是由 加载的,而 是不能委派 来加载类的,那该怎么办呢?
线程上下文类加载器正好解决了这个问题,默认情况下,Java应用的线程上下文类加载器默认是AppClassLoader,这样ServiceLoader就可以成功加载SPI的实现类了。
Tomcat与Spring的加载问题
Tomcat基本遵守了JVM的委派模型,但也在自定义的类加载器中做了细微的调整,以适应Tomcat自身的要求。下面是一张Tomcat的类加载体系图:
CommonClassLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问。
CatalinaClassLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见。
SharedClassLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见。
WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见。
稍微做一下小结:CommonClassLoader 能加载的类都可以被 CatalinaClassLoader 和 SharedClassLoader 使用,而 CatalinaClassLoader 和 SharedClassLoader 自己能加载的类则与对方相互隔离。WebAppClassLoader 可以使用 SharedClassLoader 加载到的类,但各个 WebAppClassLoader 实例之间相互隔离。
这时有的同学要问了,如果有10个web应用程序都用到了Spring的话,可以把Spring的jar包放到common或者shared目录下让这些程序共享。Spring 的作用是管理每个web应用程序的bean,getBean时自然要能访问到应用程序的类,而用户的程序显然是放在 /WebApp/WEB-INF 目录中的(由 WebAppClassLoader 加载),那么在 CommonClassLoader 或 SharedClassLoader 中的 Spring 容器如何去加载并不在其加载范围的用户程序(/WebApp/WEB-INF/)中的Class呢?
原来spring根本不会去管自己被放在哪里,它统统使用TCCL来加载类,而TCCL默认设置为了WebAppClassLoader,也就是说哪个WebApp应用调用了spring,spring就去取该应用自己的WebAppClassLoader来加载bean,简直完美~
有兴趣的可以接着看看具体实现。在web.xml中定义的listener为 ,它最终调用了 类来装载bean,具体方法如下(删去了部分不相关内容):
总结
通过前面结合SPI、Tomcat以及Spring中TCCL的应用场景,相信前面提的那两个问题就都容易解决了。最后,我们总结一下线程上下文类加载器的适用场景:
1.当高层提供了统一接口让低层去实现,同时又要是在高层加载(或实例化)低层的类时,必须通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类。
2.当使用本类托管类加载,然而加载本类的ClassLoader未知时,为了隔离不同的调用者,可以取调用者各自的线程上下文类加载器代为托管。
领取专属 10元无门槛券
私享最新 技术干货