如何破坏双亲委派模型

双亲委派模型不是一个强制性的约束模型,而是一个建议型的类加载器实现方式。考虑这么一个问题,如果基础类需要调用用户的代码该怎么办,因为根据双亲委派模型,越基础的类由越上层的加载器进行加载,但是上层的加载器并不认识用户的代码。

一个典型的例子就是JNDI服务,JNDI现在已经是Java的标准服务,它的代码由启动类加载器去加载(在JDK1.3时就放进rt.jar),但它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者(SPI, Service Provider Interface)的代码,但启动类加载器不可能“认识“这些代码啊。因为这些类不在rt.jar中,但是启动类加载器又需要加载。怎么办呢?

为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置。如果创建线程时未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认即是应用程序类加载器。

有了线程上下文加载器,JNDI服务使用这个线程上下文加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则。

JDBC如果破坏双亲委派模型

JDBC之所以要破坏双亲委派模型,是因为原生的JDBC中Driver驱动本身只是一个接口,并没有具体的实现,具体的实现是由不同的数据库去实现的,例如,可以由MySQL的mysql-connector-.jar中的Driver类具体实现。由于原生的JDBC中的类是放在rt.jar包的,是由启动类加载器进行类加载的,且需要动态去加载不同数据库类型的Driver类,而mysql-connector-.jar中的Driver类是用户自己写的代码,所以启动类加载器是不能进行加载的,需要由应用程序类加载器进行加载。此时,通过线程上下文类加载器获得应用程序类加载器,通过应用程序类加载器去加载这个Driver类,从而避开了双亲委派模型的局限性。

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访问。
  • catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见。
  • sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见。
  • WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见。

Common ClassLoader能加载的类都可以被Catalina ClassLoader和Shared ClassLoader使用,从而实现了公有类库的共用,而Catalina ClassLoader和Shared ClassLoader自己能加载的类则与对方相互隔离,从而保证了安全性。WebApp ClassLoader可以使用Shared ClassLoader加载到的类,但各个WebApp ClassLoader实例之间相互隔离。JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能,否则如果类名还是一样,旧的类加载器会直接取方法区中已经存在的,修改后的JSP是不会重新加载的。

可以看出,Tomcat没有遵循双亲委派模型,每个Webapp ClassLoader加载自己的目录下的class文件,不会传递给父类加载器。

参考资料