Tomcat是如何隔离Web应用的?

Tomcat通过自定义的类加载器WebAppClassLoader打破了双亲委托机制,目的就是为了优化加载Web应用目录下的类。Tomcat 作为 Servlet 容器,它负责加载我们Servlet 类,此外它还负责加载 Servlet 所依赖的 JAR 包。并且Tomcat 本身也是也是一个 Java 程序,因此它需要加载自己的类和依赖的 JAR 包。
如果Tomcat里面运行了两个Web应用程序,两个Web应用程序中有同名的Servlet,但功能不同,Tomcat需要同时加载和管理这两个同名的 Servlet 类,保证它们不会冲突,因此 Web 应用之间的类需要隔离。
如果如两个 Web 应用都依赖同一个第三方的 JAR 包,比如 Spring,那 Spring 的 JAR 包被加载到内存后,Tomcat 要保证这两个 Web 应用能够共享,也就是说Spring 的 JAR 包只被加载一次,否则随着依赖的第三方JAR 包增多,JVM 的内存会膨胀。
同时,跟JVM一样,我们需要隔离Tomcat本身的类和Web应用类。

一、Tomcat的类加载器的层次结构
为了解决AppClassLoader,同类名的Servlet类只能被加载一次。Tomcat 自定义一个类加载器WebAppClassLoader, 并且给每个 Web 应用创建一个类加载器实例。Context 容器组件对应一个 Web应用,因此,每个 Context 容器负责创建和维护一个WebAppClassLoader 加载器实例。原理是,不同的加载器实例加载的类被认为是不同的类,即使它们的类名相同。这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间,每一个 Web 应用都有自己的类空间,Web 应用之间通过各自的类加载器互相隔离。
在这里插入图片描述
为了Tomcat可以存放多个Web应用,Tomcat实现了Web应用的隔离,从而达到可以加载不同Web应用下相同名的Servlet类,从而编写了自己的类加载器,其内部类加载器模型如上图所示
(1)SharedClassLoader:该类加载器存在是为了解决不同Web应用之间共享类库,并且不会重复加载相同的类。它作为WebAppClassLoader的父加载器,专门加载Web应用之间的共享类。
(2)CatalinaClassloader:该类加载器专门加载Tomcat自身的类,从而和web应用的类做一个隔离。
(3)CommonClassLoad:CatalinaClassLader实现了Tomcat类和web应用类的隔离,如果二者之间需要共享一些类怎么办?这里就需要CommonClassLoad,它所加载的所有类都可以被SharedClassLoader和CatalinaClassLoader使用,从而实现web应用和tomcat对一些类的共享。
三、补充Spring的加载问题
在 JVM 的实现中有一条隐含的规则,默认情况下,如果一个类由类加载器 A 加载,那么这个类的依赖类也是由相同的类加载器加载。比如 Spring 作为一个 Bean 工厂,它需要创建业务类的实例,并且在创建业务类实例之前需要加载这些类。Spring 是通过调用Class.forName来加载业务类的。
我在前面提到,Web 应用之间共享的 JAR 包可以交给SharedClassLoader 来加载,从而避免重复加载。Spring作为共享的第三方 JAR 包,它本身是由SharedClassLoader 来加载的,Spring 又要去加载业务类,按照前面那条规则,加载 Spring 的类加载器也会用来加载业务类,但是业务类在 Web 应用目录下,不在SharedClassLoader 的加载路径下,这该怎么办呢?
于是线程上下文加载器登场了,它其实是一种类加载器传递机制。为什么叫作“线程上下文加载器”呢,因为这个类加载器保存在线程私有数据里,只要是同一个线程,一旦设置了线程上下文加载器,在线程后续执行过程中就能把这个类加载器取出来用。因此 Tomcat 为每个 Web 应用创建一个WebAppClassLoarder 类加载器,并在启动 Web 应用的线程里设置线程上下文加载器,这样 Spring 在启动时就将线程上下文加载器取出来,用来加载 Bean。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章