Java的双亲委派模型

写这篇文章的目的是什么?
太过于相信自己的记忆力以及理解能力,以至于很久之前整理过的知识又忘得一干二净,导致自己现在关于这方面的知识只有个模糊的印象,其中缘由也不是很清楚了,所以在此就觉得写篇博客记录下自己的学习过程是很有必要的。

为什么去了解它

记得在刚学数据库的时候,我们都会写上Class.forName('com.mysql.jdbc.Driver'),由于当时是初学者所以就没有具体的了解,知道后来对java的学习加深以后,就觉得摸到这块知识了,除此之外这也是一个很不错的面试知识点,可以通过这个知识来确定自己掌握的知识到底够不够。

历史

java的lClassloader从jdk1.0就已经推出,但是双亲委派模型从jdk1.2才推出,推出的目的主要是为了解决Class重复定义的问题。对于这点的解释,大家可以想象一下,如果我们自己定义了一个和java.Lang一模一样的类型,但是里面的代码却完全不同,那么jvm虚拟机在遇到这种情况到底该以谁的位标准。所以java的设计者就设计了一种双亲委派模型
其大致的解释就是,当JVM去加载一个class时,会首先让其父类加载器去加载,父加载器去判断有没有加载过此类,如果加载了子加载器就不用了加载,若没有加载父加载在有能力加载的范围内才会加载。由此可知每一种加载器的加载范围是限定的,不是所有的class都能加载,在这里贴出一张网图帮助大家理解下java类加载器的基础知识:

总结一下上图中的知识点:

  1. 三种顶级classloader(BootStrap、ExtClassLoader、AppClassLoader)
  2. 每种classloader都有自己限定加载路径或者包
  3. 三种顶级class呈现出一种"继承"的关系
  4. 自己定义的classloader继承自AppClassLoader
  5. 还有一点是图上没有的,就是父级的类加载器可以不可以使用子级类加载器加载的class,但是子级可以使用

ExtClassLoader在jdk1.9版本以及之后版本已经改名为PlatformClassLoader

抛砖引玉

在使用Mysql(5.1.6之前版本)的时候每次都要写上Class.forName('com.mysql.jdbc.Driver')这么一句代码,总感觉很多余而且还容易忘记,所以为了揭开其神秘的面纱,接下来我们就需要了解另一个知识——打破双亲委派模型

在jdk的版本迭代中曾有三次比较出名的打破双亲委派的修改

  1. 第一次是在jdk1.2中为了引出双亲委派模型,为了解决loadClass会被重写的可能,推出了一个新的方法findClass,也就是说当开发人员要定义一个自己的ClassLoader的时候只需要去重写findClass方法即可,就不会破坏掉loadClass,这样就会符合双亲委派模型。
  2. 第二次打破双亲委派模型就是本次讲解的核心内容,也是由于双亲委派模型本身的缺点,才不得不去打破
  3. 第三次是由于用户对程序动态性(代码热替换、模块热部署)的追求而导致的。如OSGi.(暂未涉及到,所以没有太深入了解,本段话摘自网上)

双亲委派模型的缺点

既然双亲委派模型这么重要,对JVM的代码安全提供着重要的保障,那它有没有什么缺点或者死穴呢?当然是有的,这里就要将jdbc驱动拿出来举例,java在设计之初为了让各自的数据库厂家提供更适合自己家数据库的jdbc驱动,就在rt.jar包里定义了一些接口,方便厂家用来扩展,如此便导致了一个重要的问题,DriverManager实在rt包里,即由高级类加载器加载,各自厂家的驱动却是在应用类加载器里,所以通过DriverManager的无法调用厂家自己实现的驱动,所以必须要开发人员手动的去加载驱动即调用Class.forName('com.mysql.jdbc.Driver')

什么是打破双亲委派模型?
双亲委派模型的基本流程就是当加载一个class的时候,首先调用父加载器去加载,当父加载器无法加载时才会由子加载器加载。所谓打破就是不按照这种模型来加载,所以rt包下定义的DriverManager是无法在自身当中去初始化各厂商实现的驱动,这个时候就会借助SPI来打破这种限制。

SPI

SPI即Service Provider Interface,服务提供接口,jdk1.6实现的一个机制,通过这个机制就可以在BootStrapClassLoader以及ExtClassLoader加载的类中获取AppClassLoader去加载应用类。该规则大致就是让开发者在jar包下的META-INF目录下创建一个services的文件,在其中添加一个用接口限定名作为文件名的文件,文件内容为接口的实现类限定名
eg:
以Mysql举例,在5.1.6版本之后就会找到META-INF/services/java.sql.Driver文件,里面添加了一行com.mysql.jdbc.Driver
SPI机制的本质其实就是通过Thread.currentThread().getContextClassLoader()(线程上下文类加载器)获取应用类加载器的。

Class.forName('com.mysql.jdbc.Driver')

Class.forName('com.mysql.jdbc.Driver')这句代码的真正意义其实是去调用Driver类中的静态初始化块,在这里回去注册一个Driver驱动,其实我们完全可以用new Driver()这行代码代替,这样也会去调用Driver的静态初始化块的,只不过这样的话DriverManager就会多注册一个Driver,与自己创建的Driver对象重复,所以就完全没有必要了。

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}