打破双亲委派之SPI、线程上下文类加载器、ServiceLoader

概述 

我们通过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的步骤顺序依次是:

  1. 通过SPI方式,读取 META-INF/services 下文件中的类名,使用TCCL加载;
  2. 通过System.getProperty("jdbc.drivers")获取设置,然后通过系统类加载器加载。
    下面详细分析SPI加载的那段代码。

直白一点说就是,我(JDK)提供了一种帮你(第三方实现者)加载服务(如数据库驱动、日志库)的便捷方式,只要你遵循约定(把类名写在/META-INF里),那当我启动时我会去扫描所有jar包里符合约定的类名,再调用forName加载,但我的ClassLoader是没法加载的,那就把它加载到当前执行线程的线程上下文加载器里,后续你想怎么操作(驱动实现类的static代码块)就是你的事了。
 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值