🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
### Tomcat类加载器层次 Tomcat 作为`Servlet`容器,它负责加载我们的`Servlet`类,此外它还负责加载`Servlet`所依赖的 JAR 包。并且`Tomcat`本身也是也是一个 Java 程序,因此它需要加载自己的类和依赖的 JAR 包。首先让我们思考这一下这几个问题: 1. 假如我们在 Tomcat 中运行了两个 Web 应用程序,两个 Web 应用中有同名的`Servlet`,但是功能不同,Tomcat 需要同时加载和管理这两个同名的`Servlet`类,保证它们不会冲突,因此 Web 应用之间的类需要隔离。 2. 假如两个 Web 应用都依赖同一个第三方的 JAR 包,比如`Spring`,那`Spring`的 JAR 包被加载到内存后,`Tomcat`要保证这两个 Web 应用能够共享,也就是说`Spring`的 JAR 包只被加载一次,否则随着依赖的第三方 JAR 包增多,`JVM`的内存会膨胀。 3. 跟 JVM 一样,我们需要隔离 Tomcat 本身的类和 Web 应用的类 ![](https://img.kancloud.cn/e0/9b/e09b5de9b96e4fb1be65fe5fce5c2aa9_569x485.png) 具体介绍下: * **CommonClassLoader**:以应用类加载器为父类,是tomcat顶层的公用类加载器,其路径由conf/catalina.properties中的common.loader指定,默认指向${catalina.home}/lib下的包。 * **CatalinaClassloader**:以Common类加载器为父类,是用于加载Tomcat应用服务器的类加载器,其路径由server.loader指定,默认为空,此时tomcat使用Common类加载器加载应用服务器。 * **SharedClassLoader**:以Common类加载器为父类,是所有Web应用的父类加载器,其路径由shared.loader指定,默认为空,此时tomcat使用Common类加载器作为Web应用的父加载器。本质需求是两个 Web 应用之间怎么共享库类,并且不能重复加载相同的类。在双亲委托机制里,各个子加载器都可以通过父加载器去加载类,那么把需要共享的类放到父加载器的加载路径下不就行了吗。 因此 Tomcat 的设计者又加了一个类加载器`SharedClassLoader`,作为`WebAppClassLoader`的父加载器,专门来加载 Web 应用之间共享的类。如果`WebAppClassLoader`自己没有加载到某个类,就会委托父加载器`SharedClassLoader`去加载这个类,`SharedClassLoader`会在指定目录下加载共享类,之后返回给`WebAppClassLoader`,例如多个web应用都依赖的spring就可以使用SharedClassLoader进行加载; * **WebAppClassLoader**:以Shared类加载器为父类,加载/WEB-INF/classes目录下的未压缩的Class和资源文件以及/WEB-INF/lib目录下的jar包,该类加载器只对当前Web应用可见,对其他Web应用均不可见;Tomcat 的解决方案是自定义一个类加载器`WebAppClassLoader`, 并且给每个 Web 应用创建一个类加载器实例。我们知道,Context 容器组件对应一个 Web 应用,因此,每个`Context`容器负责创建和维护一个`WebAppClassLoader`加载器实例。这背后的原理是,**不同的加载器实例加载的类被认为是不同的类**,即使它们的类名相同。这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间,每一个 Web 应用都有自己的类空间,Web 应用之间通过各自的类加载器互相隔离。 #### 如何隔离 Tomcat 本身的类和 Web 应用的类? 要共享可以通过父子关系,要隔离那就需要兄弟关系了。兄弟关系就是指两个类加载器是平行的,它们可能拥有同一个父加载器,基于此 Tomcat 又设计一个类加载器`CatalinaClassloader`,专门来加载 Tomcat 自身的类。 这样设计有个问题,那 Tomcat 和各 Web 应用之间需要共享一些类时该怎么办呢? 老办法,还是再增加一个`CommonClassLoader`,作为`CatalinaClassloader`和`SharedClassLoader`的父加载器。`CommonClassLoader`能加载的类都可以被`CatalinaClassLoader`和`SharedClassLoader`使用 ***** 默认情况下,Common、Catalina、Shared类加载器是同一个,但可以配置3个不同的类加载器,使他们各司其职 > Common类加载器复杂加载Tomcat应用服务器内部和Web应用均可见的类,如Servlet规范相关包和一些通用工具包。 > Catalina类加载器负责只有Tomcat应用服务器内部可见的类,这些类对Web应用不可见。比如,想实现自己的会话存储方案,而且该方案依赖了一些第三方包,当然是不希望这些包对Web应用可见,这时可以配置server.load,创建独立的Catalina类加载器。 > Shared类复杂加载Web应用共享类,这些类tomcat服务器不会依赖 ***** Q:如果有10个Web应用程序都是用Spring来进行组织和管理的话,可以把Spring放到Common或Shared目录下让这些程序共享。Spring要对用户程序的类进行管理,自然要能访问到用户程序的类,而用户的程序显然是放在/WebApp/WEB-INF目录中的,那么被CommonClassLoader或SharedClassLoader加载的Spring如何访问并不在其加载范围内的用户程序呢? 如果按主流的双亲委派机制,显然无法做到让父类加载器加载的类去访问子类加载器加载的类,但使用线程上下文加载器,可以让父类加载器请求子类加载器去完成类加载的动作。spring加载类所用的Classloader是通过Thread.currentThread().getContextClassLoader()来获取的,而当线程创建时会默认setContextClassLoader(AppClassLoader),即线程上下文类加载器被设置为AppClassLoader,spring中始终可以获取到这个AppClassLoader(在Tomcat里就是WebAppClassLoader)子类加载器来加载bean,以后任何一个线程都可以通过getContextClassLoader()获取到WebAppClassLoader来getbean了