Tomcat类加载器破坏双亲委派

转载:http://www.javashuo.com/article/p-nqgssvfe-d.htmlhtml

http://www.cnblogs.com/aspirant/p/8991830.htmljava

http://www.cnblogs.com/xing901022/p/4574961.htmlweb

双亲委派模式的破坏

第一次破坏:向前兼容

双亲委派模型的第一次“被破坏”其实发生在双亲委派模型出现以前–即JDK1.2发布以前。因为双亲委派模型是在JDK1.2以后才被引入的,而类加载器和抽象类java.lang.ClassLoader则是JDK1.0时候就已经存在,面对已经存在 的用户自定义类加载器的实现代码,Java设计者引入双亲委派模型时不得不作出一些妥协。为了向前兼容,JDK1.2以后的java.lang.ClassLoader添加了一个新的proceted方法findClass(),在此以前,用户去继承java.lang.ClassLoader的惟一目的就是重写loadClass()方法,由于虚拟在进行类加载的时候会调用加载器的私有方法loadClassInternal(),而这个方法的惟一逻辑就是去调用本身的loadClass()。JDK1.2以后已再也不提倡用户再去覆盖loadClass()方法,应当把本身的类加载逻辑写到findClass()方法中,在loadClass()方法的逻辑里,若是父类加载器加载失败,则会调用本身的findClass()方法来完成加载,这样就能够保证新写出来的类加载器是符合双亲委派模型的。 面试

第二次破坏:加载SPI接口实现类

双亲委派模型的第二次“被破坏”是这个模型自身的缺陷所致使的,双亲委派模型很好地解决了各个类加载器的基础类统一问题(越基础的类由越上层的加载器进行加载),基础类之因此被称为“基础”,是由于它们老是做为被调用代码调用的API。可是,若是基础类又要调用用户的代码,那该怎么办呢。 
这并不是是不可能的事情,一个典型的例子即是JNDI服务,它的代码由启动类加载器去加载(在JDK1.3时放进rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它须要调用独立厂商实现部部署在应用程序的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代码,但启动类加载器不可能“认识”之些代码,该怎么办? 
为了解决这个困境,Java设计团队只好引入了一个不太优雅的设计:线程上下文件类加载器(Thread Context ClassLoader)。这个类加载器能够经过java.lang.Thread类的setContextClassLoader()方法进行设置,若是建立线程时还未设置,它将会从父线程中继承一个;若是在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是应用程序类加载器。了有线程上下文类加载器,JNDI服务使用这个线程上下文类加载器去加载所须要的SPI代码,也就是父类加载器请求子类加载器去完成类加载动做,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型,但这也是迫不得已的事情。Java中全部涉及SPI的加载动做基本上都采用这种方式,例如JNDI,JDBC,JCE,JAXB和JBI等。缓存

第三次破坏:热部署

双亲委派模型的第三次“被破坏”是因为用户对程序的动态性的追求致使的。为了实现热插拔,热部署,模块化,意思是添加一个功能或减去一个功能不用重启,只须要把这模块连同类加载器一块儿换掉就实现了代码的热替换。例如OSGi的出现。在OSGi环境下,类加载器再也不是双亲委派模型中的树状结构,而是进一步发展为网状结构。tomcat

Java 程序中基本有一个共识:OSGI对类加载器的使用时值得学习的,弄懂了OSGI的实现,就能够算是掌握了类加载器的精髓。安全

Tomcat类加载器

抛出问题

一、既然 Tomcat 不遵循双亲委派机制,那么若是我本身定义一个恶意的HashMap,会不会有风险呢?(阿里的面试官问)服务器

答: 显然不会有风险,若是有,Tomcat都运行这么多年了,那群Tomcat大神能不改进吗? tomcat不遵循双亲委派机制,只是自定义的classLoader顺序不一样,但顶层仍是相同的,数据结构

仍是要去顶层请求classloader.app

二、咱们思考一下:Tomcat是个web容器, 那么它要解决什么问题: 
1. 一个web容器可能须要部署两个应用程序,不一样的应用程序可能会依赖同一个第三方类库的不一样版本,不能要求同一个类库在同一个服务器只有一份,所以要保证每一个应用程序的类库都是独立的,保证相互隔离。 
2. 部署在同一个web容器中相同的类库相同的版本能够共享。不然,若是服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机,这是扯淡的。 
3. web容器也有本身依赖的类库,不能于应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。 
4. web容器要支持jsp的修改,咱们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已是司空见惯的事情,不然要你何用? 因此,web容器须要支持 jsp 修改后不用重启。

再看看咱们的问题:Tomcat 若是使用默认的类加载机制行不行? 
答案是不行的。为何?咱们看,第一个问题,若是使用默认的类加载器机制,那么是没法加载两个相同类库的不一样版本的,默认的类加载器是无论你是什么版本的,只在意你的全限定类名,而且只有一份。第二个问题,默认的类加载器是可以实现的,由于他的职责就是保证惟一性。第三个问题和第一个问题同样。咱们再看第四个问题,咱们想咱们要怎么实现jsp文件的热修改(楼主起的名字),jsp 文件其实也就是class文件,那么若是修改了,但类名仍是同样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会从新加载的。那么怎么办呢?咱们能够直接卸载掉这jsp文件的类加载器,因此你应该想到了,每一个jsp文件对应一个惟一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。从新建立类加载器,从新加载jsp文件。

Tomcat 如何实现本身独特的类加载机制?

因此,Tomcat 是怎么实现的呢?牛逼的Tomcat团队已经设计好了。咱们看看他们的设计图:

咱们看到,前面3个类加载和默认的一致,CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader则是Tomcat本身定义的类加载器,它们分别加载/common/*/server/*/shared/*(在tomcat 6以后已经合并到根目录下的lib目录下)和/WebApp/WEB-INF/*中的Java类库。其中WebApp类加载器和Jsp类加载器一般会存在多个实例,每个Web应用程序对应一个WebApp类加载器,每个JSP文件对应一个Jsp类加载器。

  • commonLoader:Tomcat最基本的类加载器,加载路径中的class能够被Tomcat容器自己以及各个Webapp(web应用)访问;
  • catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
  • sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于全部Webapp可见,可是对于Tomcat容器不可见;
  • WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;

从图中的委派关系中能够看出:

CommonClassLoader能加载的类均可以被Catalina ClassLoader和SharedClassLoader使用,从而实现了公有类库的共用,而CatalinaClassLoader和Shared ClassLoader本身能加载的类则与对方相互隔离。

WebAppClassLoader可使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。

而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并经过再创建一个新的Jsp类加载器来实现JSP文件的HotSwap功能。

好了,至此,咱们已经知道了tomcat为何要这么设计,以及是如何设计的,那么,tomcat 违背了java 推荐的双亲委派模型了吗?答案是:违背了。 咱们前面说过:

双亲委派模型要求除了顶层的启动类加载器以外,其他的类加载器都应当由本身的父类加载器加载。

很显然,tomcat 不是这样实现,tomcat 为了实现隔离性,没有遵照这个约定,每一个webappClassLoader加载本身的目录下的class文件,不会传递给父类加载器。

咱们扩展出一个问题:若是tomcat 的 Common ClassLoader 想加载 WebApp ClassLoader 中的类,该怎么办?

看了前面的关于破坏双亲委派模型的内容,咱们内心有数了,咱们可使用线程上下文类加载器实现,使用线程上下文加载器,可让父类加载器请求子类加载器去完成类加载的动做。

Tomcat类加载过程

tomcat的类加载机制是违反了双亲委托原则的,对于一些未加载的非基础类(Object,String等),各个web应用本身的类加载器(WebAppClassLoader)会优先加载,加载不到时再交给commonClassLoader走双亲委托。具体的加载逻辑位于WebAppClassLoaderBase.loadClass()方法中,代码篇幅长,这里以文字描述加载一个类过程:

  1. 先在本地缓存中查找是否已经加载过该类(对于一些已经加载了的类,会被缓存在resourceEntries这个数据结构中),若是已经加载即返回,不然 继续下一步。
  2. 让系统类加载器(AppClassLoader)尝试加载该类,主要是为了防止一些基础类会被web中的类覆盖,若是加载到即返回,返回继续。
  3. 前两步均没加载到目标类,那么web应用的类加载器将自行加载,若是加载到则返回,不然继续下一步。
  4. 最后仍是加载不到的话,则委托父类加载器(Common ClassLoader)去加载。

第3第4两个步骤的顺序已经违反了双亲委托机制,除了tomcat以外,JDBC,JNDI,Thread.currentThread().setContextClassLoader();等不少地方都同样是违反了双亲委托。

相关文章
相关标签/搜索