双亲委派模型是类加载阶段中类加载器加载类的实现方式,所以先来了解下类加载器~
类加载器
类加载器是JAVA中的核心组件。类加载器把类进行加载、连接、初始化的这一阶段称为类加载阶段,类加载阶段需要通过类的全限定名来获取定义了此类的二进制字节流。类加载器将该字节流转化为具体的类对象,存放在JVM的方法区中,作为该类的访问入口。
在Java中任意一个类都是由这个类本身和加载这个类的类加载器来确定这个类在JVM中的唯一性。
类加载器除了能用来加载类,还能用来作为类的层次划分。Java自身提供了3种类加载器:
类加载器 | 介绍 |
---|---|
启动类加载器(Bootstrap ClassLoader) | 属于虚拟机自身的一部分,用C++实现的,主要负责加载<JAVA_HOME>\lib目录中或被-Xbootclasspath指定的路径中的并且文件名是被虚拟机识别的文件。,是所有类加载器的父类。 |
扩展类加载器(Extension ClassLoader) | 它是用Java实现的,独立于虚拟机,主要负责加载<JAVA_HOME>\lib\ext目录中或被java.ext.dirs系统变量所指定的路径的类库。 |
应用类加载器(Application ClassLoader) | 它是用Java实现的,独立于虚拟机。主要负责加载ClassPath下也就是工程目录下的class文件和jar包。所以我们平时写的自定义类默认都是通过应用类加载器来进行加载的。 |
除了Java自身提供的以上这三种类加载器,我们还可以自定义类加载器。一般的做法就是继承ClassLoader抽象类,然后重写findClass方法,在里面加载好对应的字节数组,然后作为参数传给defineClass方法,完成真正的类加载。
双亲委派模型
双亲委派的意思是:如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。这里的双亲其实就指的是父类,没有mother。父类也不是我们平日所说的那种继承关系,只是调用逻辑是这样。
双亲委派的好处
它使得类有了层次的划分。就拿java.lang.Object来说,你加载它经过一层层委托最终是由Bootstrap ClassLoader来加载的,也就是最终都是由Bootstrap ClassLoader去找<JAVA_HOME>\lib中rt.jar里面的java.lang.Object加载到JVM中。
这样如果有不法分子自己造了个java.lang.Object,里面嵌了不好的代码,如果我们是按照双亲委派模型来实现的话,最终加载到JVM中的只会是我们rt.jar里面的东西,也就是这些核心的基础类代码得到了保护。因为这个机制使得系统中只会出现一个java.lang.Object。不会乱套了。你想想如果我们JVM里面有两个Object,那岂不是天下大乱了。
因此既然推荐使用这种模型当然是有道理了。
但是人生不如意事十之八九,有些情况不得不违反这个约束,例如JDBC。
你先得知道SPI(Service Provider Interface),这玩意和API不一样,它是面向拓展的,也就是我定义了这个SPI,具体如何实现由扩展者实现。我就是定了个规矩。
JDBC就是如此,在rt里面定义了这个SPI,那mysql有mysql的jdbc实现,oracle有oracle的jdbc实现,反正我java不管你内部如何实现的,反正你们都得统一按我这个来,这样我们java开发者才能容易的调用数据库操作。
所以因为这样那就不得不违反这个约束啊,Bootstrap ClassLoader就得委托子类来加载数据库厂商们提供的具体实现。因为它的手只能摸到<JAVA_HOME>\lib中,其他的它无能为力。这就违反了自下而上的委托机制了。
Java就搞了个线程上下文类加载器,通过setContextClassLoader()默认情况就是应用程序类加载器然后Thread.current.currentThread().getContextClassLoader()获得类加载器来加载。