概述
我们通过JDBC的实现机制来讲解
// 加载Class到AppClassLoader(应用类加载器),然后注册驱动类
// Class.forName("com.mysql.jdbc.Driver").newInstance();
String url = "jdbc:mysql://localhost:3306/web";
// 通过java库获取数据库连接
Connection conn = java.sql.DriverManager.getConnection(url, "root", "root");
以上就是mysql注册驱动及获取connection的过程,各位可以发现经常写的Class.forName被注释掉了,但依然可以正常运行,这是为什么呢?这是因为从Java1.6开始自带的jdbc4.0版本已支持SPI服务加载机制,只要mysql的jar包在类路径中,就可以注册mysql驱动。
我们来分析分析DriverManager,从它的静态方法入手:
一、DriverManager分析
首先看它的静态代码块,当我们第一次使用到该类的静态方法时就会调用,我们用它来加载数据库的驱动,就肯定会触发它的loadInitialDrivers()方法,还有一点注意的是那个println()方法不是给我们看的不会输出到控制台。
进入到loadInitialDrivers(),因为代码太多我就截取一部分,有兴趣的可以看源码。
可以看到关键部分调用到了ServiceLoader的load()方法,参数里面放了一个系统自带的驱动类,该类全限定名为java.sql.Driver,跟着进入该方法:
该方法首先获取线程上下文类加载器,该类加载器可以看到下面的Launcher源码,默认设置为APP类加载器。
我们再我往下继续看,进入ServiceLoader.load(service, cl);参数多了一个classLoader。
该方法新创建一个ServiceLoader的实例,并且返回,该实例最终会返回到DriverManager的loadInitialDrivers(),我们在这里停一下,首先来看看该ServiceLoader实例是何方神圣,再去接着DriverManager那里分析。
二、ServiceLoader分析
首先看ServiceLoader的一些用得到的属性,它实现了迭代器接口
刚刚使用到的构造方法如下:
看第一个箭头,它就是给service属性赋值为传过来的Class,它就是java.sql.Driver类,且给loader属性赋值为我们默认的AppClassLoader,再看第二个箭头的reload()方法:
再继续给我们的lookuoIterator属性赋值为一个新的LazyIterator实例
LazyIterator意为懒加载迭代器,是ServiceLoader的一个类部类,它因为继承了Iterator接口,所以,有hasNext()和next()方法,且这两个方法实际调用的是自己的hasNextService()和nextService()
先看hasNextService()方法
parse()方法就是读取文件的全部的内容,弄成一个迭代器,挂到nextName上。
我们看nextService()方法,就是通过nextName一次一次的迭代完一个文件的所有内容,当一个文件读取完了可能还要其他同名的,继续读取……
更重要的是读取到一条全限定名后,就用forName()加载该类,用到的加载器是传过来的AppClassLoader加载,加载完后实例化,并且放到ServiceLoader的Map容器里,返回。
上面的Class.forName()为什么要指定类加载器呢?这就是本篇的关键了
我们的ServiceLoader和DriverManager类都是系统类,它们都是启动类加载器加载的,这两个类如果调用Class.forName()不加上ClassLoader,那么看该方法的源码,默认就是通过调用者的类的类加载器加载。
但是我们的数据库jar包是在类数据下的,启动类加载器加载不了,也无法委派给父加载器加载,所以我们就需要破坏双亲委派机制,指定一个类加载器去加载。
到这我们的ServiceLoader就基本分析结束了,这就SPI的具体实现方法,如数据库实现商只要在jar包中放一个约定的文件,其中写上需要数据库驱动的实现类即可。
三、继续分析
下面就是DriverManager剩余的代码,也是和我门上面分析的一样喔
从上面可以看出JDBC中的DriverManager
的加载Driver的步骤顺序依次是:
- 通过SPI方式,读取 META-INF/services 下文件中的类名,使用TCCL加载;
- 通过
System.getProperty("jdbc.drivers")
获取设置,然后通过系统类加载器加载。
下面详细分析SPI加载的那段代码。
直白一点说就是,我(JDK)提供了一种帮你(第三方实现者)加载服务(如数据库驱动、日志库)的便捷方式,只要你遵循约定(把类名写在/META-INF里),那当我启动时我会去扫描所有jar包里符合约定的类名,再调用forName加载,但我的ClassLoader是没法加载的,那就把它加载到当前执行线程的线程上下文加载器里,后续你想怎么操作(驱动实现类的static代码块)就是你的事了。