Java默认的类加载机制是通过双亲委派模型来实现的。而Tomcat实现的方式又和双亲委派模型有所区别。原因在于一个Tomcat容器允许同时运行多个Web程序,每个Web程序依赖的类又必须是相互隔离的。因此,如果Tomcat使用双亲委派模式来加载类的话,将导致Web程序依赖的类变为共享的。
双亲委派模式
Java是一门面向对象的语言,而对象又必然依托于类。类要运行,必须首先被加载到内存。我们可以简单地把类分为几类:
1.Java自带的核心类
2.Java支持的可扩展类
3.我们自己编写的类
如果所有的类都使用一个类加载器来加载,会出现什么问题呢?
假如我们自己编写一个类java.util.Object,它的实现可能有一定的危险性或者隐藏的bug。而我们知道Java自带的核心类里面也有java.util.Object,如果JVM启动的时候先行加载的是我们自己编写的java.util.Object,那么就有可能出现安全问题!
所以,Sun(后被Oracle收购)采用了另外一种方式来保证最基本的、也是最核心的功能不会被破坏。你猜的没错,那就是双亲委派模式!
双亲委派模式对类加载器定义了层级,每个类加载器都有一个父类加载器。在一个类需要加载的时候,首先委派给父类加载器来加载,而父类加载器又委派给祖父类加载器来加载,以此类推。如果父类及上面的类加载器都加载不了,那么由当前类加载器来加载,并将被加载的类缓存起来。
1.java自带的核心类 -- 由启动类加载器加载
2.Java支持的可扩展类 -- 由扩展类加载器加载
3.我们自己编写的类 -- 默认由应用程序类加载器或其子类加载
双亲委派模型解决了类错乱加载的问题,也设计得非常精妙。但它也不是万能的,在有些场景也会遇到它解决不了的问题。哪些场景呢?我们举一个例子来看看。
在Java核心类里面有SPI(Service Provider Interface),它由Sun编写规范,第三方来负责实现。SPI需要用到第三方实现类。如果使用双亲委派模型,那么第三方实现类也需要放在Java核心类里面才可以,不然的话第三方实现类将不能被加载使用。但是这显然是不合理的!怎么办呢?ContextClassLoader(上下文类加载器)就来解围了。
在java.lang.Thread里面有两个方法,get/set上下文类加载器
1.public void setContextClassLoader(ClassLoader cl)
2.public ClassLoader getContextClassLoader()
我们可以通过在SPI类里面调用getContextClassLoader来获取第三方实现类的类加载器。由第三方实现类通过调用setContextClassLoader来传入自己实现的类加载器。这样就变相地解决了双亲委派模式遇到的问题。但是很显然,这种机制破坏了双亲委派模式。
Tomcat类加载机制
Tomcat类加载图
我们在这张图中看到很多类加载器,除了Jdk自带的类加载器,我们尤其关心Tomcat自身持有的类加载器。仔细一点我们很容易发现:Catalina类加载器和Shared类加载器,他们并不是父子关系,而是兄弟关系。为啥这样设计,我们得分析一下每个类加载器的用途,才能知晓。
1.Common类加载器,负责加载Tomcat和Web应用都复用的类
2.Catalina类加载器,负责加载Tomcat专用的类,而这些被加载的类在Web应用中将不可见
3.Shared类加载器,负责加载Tomcat下所有的Web应用程序都复用的类,而这些被加载的类在Tomcat中将不可见
4.WebApp类加载器,负责加载具体的某个Web应用程序所使用到的类,而这些被加载的类在Tomcat和其他的Web应用程序都将不可见
5.Jsp类加载器,每个jsp页面一个类加载器,不同的jsp页面有不同的类加载器,方便实现jsp页面的热插拔
链接:https://www.jianshu.com/p/51b2c50c58eb