相关文章:
双亲委派模型并不是一个强制性的约束模型,而是 Java 设计者推荐给开发者的类加载器实现方式,在 Java 的世界中大部分的类加载器都遵循这个模型,但也有例外,到目前为止,双亲委派模型主要出现过三次较大规模的 “被破坏” 情况
一、第一次被破坏
-
发生在双亲委派模型出现之前,双亲委派模型在 JDK2.0 之后才被引入,为了向前兼容 JDK1.0 已经存在的自定义类加载器,JDK2.0 之后的 java.lang.ClassLoader 添加了一个新的 protected 方法 findClass()
-
在此之前,我们去继承 java.lang.ClassLoader 的唯一目的就是为了重写 loadClass() 方法,因为虚拟机在进行类加载的时候会调用加载器的私有方法 loadClassInternal(),而这个方法的唯一逻辑就是去调用自己的 loadClass() 方法
-
JDK2.0 之后已经不再提倡去覆盖 loadClass() 方法,而应当把自己的类加载逻辑写到 findClass() 方法当中,在 loadClass() 方法的逻辑里,如果父类加载器加载失败,则会调用自己的 findClass() 方法来完成加载,这样就可以保证自定义的类加载器是遵循双亲委派模型的
二、第二次被破坏
-
由双亲委派模型自身的缺陷所导致,双亲委派很好地解决了各个类加载器基础类的统一问题 (越基础的类由越上层的加载器进行加载),如果基础类又要回调用户的代码,就会出现问题
-
一个典型的例子便是 JNDI (Java Naming and Directory Interface:Java 命名与目录接口) 服务,它的代码由启动类加载器去加载 (在 JDK3.0 时放入 rt.jar 中),但 JNDI 的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序 CLASSPATH 下的 JNDI 接口提供者 (SPI:Service Provider Interface) 的代码,但启动类无法加载这些代码
-
为了解决这个问题,引入了线程上下文类加载器 (Thread Context ClassLoader),可以通过 java.lang.Thread 类的 setContextClassLoaser() 方法进行设置
-
如果创建线程时还未设置,它将会从父线程中继承一个
-
如果在应用程序的全局范围内都没有设置过的话,那么这个类加载器默认就是应用程序类加载器
-
-
JNDI 服务可以使用这个线程上下文类加载器去加载所需要的 SPI 代码,也就是父类加载器委派子类加载器去完成类的加载动作,这种行为实际上逆向使用了类加载器,已经违背了双亲委派模型的一般性原则
三、第三次被破坏
-
由用户对程序动态性的追求所导致,这里的动态性指的是:代码热替换 (HotSwap)、模块热部署 (Hot Deployment)
-
提到动态性,那就不能不提到 OSGI (Open Service Gateway Initiative:面向 Java 的动态模型系统),目前 OSGI 已经成为业界 “事实上” 的 Java 模块化标准,OSGI 实现模块化热部署的关键在于其自定义的类加载器机制的实现
-
每一个程序模块 (OSGI 中称为 Bundle) 都有一个自己的类加载器,当需要更换一个 Bundle 时,就把 Bundle 连同类加载器一起换掉以实现代码的热替换