参考:https://blog.csdn.net/fuzhongmin05/article/details/57404890
在此之前,描述一下类加载器:
类的加载阶段,根据类的全限定名找到对应的二进制字节流并没有定义具体的实现方式,是通过类加载器完成的。一个类加载器本身也是一个Java类。
JVM中内嵌了一个Bootstrap类加载器(启动类加载器),它使用特定于操作系统的本地代码实现的,这个类装载器不用专门的类加载器加载。Bootstrap负责加载Java核心包中的类(rt.jar),这些类的class.getClassLoader()方法返回值为null。该核心包中有两个类加载器:ExtClassLoader(扩展类加载器)和AppClassLoader(应用程序类加载器)。AppClassLoader的父类加载器是ExtClassLoader。ExtClassLoader负责加载扩展API,AppClassLoader负责加载classpath目录下的类。
此外,通过classloader加载类不会对类进行解析和初始化,但是class.forName("类名")则会进行解析和初始化。
一个功能健全的Web容器,需要解决如下几个问题:
1)部署在同一个Web容器上的两个Web应用程序所使用的Java类库可以实现相互分离。
两个不同的应用程序可能依赖同一个第三方类库的不同版本(Spring 2.0,Spring 3.0),不能要求一个类库在一个服务器中只有一份,服务器应当保证两个应用程序的类库可以相互独立使用。
2)部署在同一个Web容器上的两个Web应用程序所使用的Java类库可以共享。
相同的类库可以实现共享,如果不能共享,虚拟机方法区很容易出现过度膨胀的风险。
3)Web容器尽可能保证自身的安全不受部署的Web应用程序影响。
许多主流的Java Web容器自身也是使用Java语言来实现的。一般来说,基于安全考虑,容器所使用的类库应该与应用程序的类库相互独立。
4)支持JSP应用的Web容器,大多数需要支持HotSwap功能。
要实现上述的特性,在部署Web应用时,单独的一个Class Path就无法满足需求了,所以,各种Web容器都提供了好几个Class Path路径供用户存放第三方类库,这些路径一般都以“lib”或者“classes”命名。被放置到不同路径中的类库,具备不同的访问范围和服务对象。通常,每一个目录都会有一个相应的自定义类加载器去加载放置在里面的Java类库。
以Tomcat容器为例,在Tomcat目录结构中,有3组目录(“/common/*”、“/server/*”和“/shared/*”)可以存放Java类库,另外还可以加上Web应用程序自身的目录“/WEB-INF/*”。
- 放置在/common目录中:类库可以被Tomcat和所有Web应用程序共同使用
- 放置在/server目录中:类库可以被Tomcat使用,对所有的Web应用程序不可见
- 放置在/shared目录中:类库可以被所有的Web应用程序共同使用,但对Tomcat自己不可见。
- 放置在/WebApp/WEB-INF目录中:类库仅仅被此Web应用程序使用,对Tomcat和其他Web程序不可见
为了支持这套目录结构,并对目录里面的类库进行加载和隔离,Tomcat自定义了多个类加载器,如下:
其中CatalinaClassLoader加载/server/*中的Java类库,每个WebApp类加载器和Jsp类加载器存在多个实例,每个Web应用程序对应一个WebApp类加载器,每个Jsp文件对应一个Jsp类加载器。
从图中的委派关系中可以看出,CommonClassLoader能加载的类都可以被Catalina ClassLoader和SharedClassLoader使用,而CatalinaClassLoader和Shared ClassLoader自己能加载的类则与对方相互隔离。WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。
如果有10个Web应用程序都是用Spring进行组织和管理,可以把Spring放在Common或Shared目录下让这些程序共享。Spring要对用户程序的类进行管理,自然要访问到用户程序的类,而用户的程序显然是放在/WebApp/WEB-INF目录下,那么被Common ClassLoader或者Shared ClassLoader加载的Spring如何访问并不在其加载范围的用户程序呢?
使用线程上下文类加载器实现,可以让父类加载器委托子类加载器完成类加载的动作。Spring加载类的ClassLoader是通过Thread.currentThread().getContextClassLoader()来获取的,当线程创建时会默认设置上下文加载器为AppClassLoader,Spring用这个AppClassLoader(在Tomcat里就是WebAppClassLoader)子类加载器来加载bean,以后任何一个线程都可以通过getContextClassLoader()获取到WebAppClassLoader来getBean。