MySQL驱动加载
传统方法
使用Java代码连接MySQL需要走以下流程(使用框架也要做对应的配置):
1 | public static void main(String[] args) throws ClassNotFoundException, SQLException { |
这里边涉及到一个问题,为什么一定要写一句Class.forName(driverClassName)
?
这里首先涉及到Java的类加载机制
想要使用一个类,则必须要求该类已经被加载到JVM中,加载的过程实际上就是通过类的全限定名来获取定义该类二进制字节流,然后将这个字节流所表示的静态存储结构转换为方法去的动态运行时数据结构。同时在在内存中实例化一个java.lang.Class对象,作为方法区中该类的数据访问入口(供我们使用)。
—— 出自《深入理解Java虚拟机》
其实在一开始,我并不了解Class.forName()
是干嘛的,后边了解到它用作加载类,官方解释为:在运行时动态的加载一个类,返回值为生成的Class对象。所以这行代码的目的,就是将com.mysql.cj.jdbc.Driver
类加载到Jvm中了。
这里,forName
方法的具体实现如下:
1 |
|
到这里,也就是说这一行代码不仅加载了对应的类,也做了初始化操作。
至于说后续为什么可以直接在DriverManager
使用,就要看Driver类里面实现了什么。
MySQL的驱动实现了Java官方提供的Driver接口,这也是每一个数据库厂商所必须要做的事情。而且他们都需要以下这段代码:
1 | public class Driver extends NonRegisteringDriver implements java.sql.Driver { |
该类中定义了一个静态代码块,静态代码快中创建了一个驱动类实例注册给了DriverManager,而静态代码块的内容会在初始化的过程中执行,所以才能通过DriverManager.getConnection
直接获取一个连接。
打破双亲委派机制
在jdbc4.0之后,使用了spi机制,破坏了双亲委派机制。也就是说我们不再需要写哪一行Class.forName(driverClassName);
我们只需要将对应的驱动类的jar包放到工程的class path
下,驱动类会 自动被加载。
SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。
SPI的目的是为了提前使用某些未被实现的方法。定义一组接口,然后直接通过接口使用它的方法,但是这些方法还未被实现,留给第三方去实现,这就是spi的目的。
还有一种说法,SPI,为了解耦,从配置里获取某个接口的具体实现类。
为了支持这个新特性,各个数据库厂商的jar包都有一个META-INF/services
目录,里面有一个java.sql.Driver
,这里指定了driver的全限定名。

存在的问题
JDBC的driver接口是定义在JDK中的,但是它的实现类,确在一个jar包中,放在classpath下。就存在以下问题:
DriverManager
类会加载每个Driver接口的实现类并管理它们,但是DriverManager
类自身是jre/lib/rt.jar
里的类,是由bootstrap classloader
加载的。- 根据类加载机制,某个类需要引用其它类的时候,虚拟机将会用这个类的classloader去加载被引用的类,但是
bootstrap classloader
是无法加载这个driver的(bootstrap classloader
只能加载Java 的核心类库包)。 - 因此只能在DriverManager里强行指定下层classloader来加载Driver实现类,而这就会打破双亲委派模型。
具体的做法是,添加了一个线程上下文类加载器Thread Context ClassLoader
,在启动类加载器中获取应用程序类加载器。Thread.setContextClassLoaser() 设置线程上下文类加载器,如果创建线程的时候没有设置,会从父类继承一个,默认应用程序类加载器。