01
前言
初次接触Java Web的时候相信都接触过JDBC连接和JSP页面,不知是知识传授的理所当然还是初次接触这些知识的不熟悉,当时对创建JDBC连接的代码没有任何感觉和疑问,现在回想起来,创建JDBC连接的过程还是值得一看的。
02
问题
先看下面一段代码:

这是一段简单的JDBC连接语句,但是看过这段代码之后,不知道大家会不会产生和我一样的疑问:
Class.forName(jdbcName); 这句与之后的
con = DriverManager.getConnection(dbUrl,dbUserName, dbPassword);
他们是怎么联系起来的呢?
了解 Java 基本语法的都知道,Java中类或者对象的调用是通过定义变量名称来引用的,比如private Student studentObj = new Student();
其中“private”指定变量的范围是私有的,第一个“Student”说明了声明的类型为Student类,“studentObj”是后面生成的Student对象的引用名称,“new Student()”是创建了一个新的student对象。
然后通过“studentObj”就可以使用“Student”对象中定义的各种属性和方法。我们大多数的使用方式是上面这种,但是创建JDBC connection显然不是这种情况。
从代码中可以看到,两句之间并没有变量传递,也没有什么 静态变量 定义。“TestClass”也没有继承父类,那么通过Class.forname()函数加载的驱动是如何被DriverManager.getConnection() 函数所使用的呢?
03
分析
进到源码中:

上述代码中每个方法再往里看看是什么样子:
Reflection.getCallerClass()

forName0(className,true,ClassLoader.getClassLoader(caller), caller)


都是native方法了,没法再进入下层。
至此好像还没有什么头绪,那不妨看看
con = DriverManager.getConnection(dbUrl, dbUserName, dbPassword); 这句话里面做了什么

getConnection(url, info, Reflection.getCallerClass())


这里面的 registeredDrivers 在哪里定义的呢?使用在这里

这是一个公有的静态方法,在对应的驱动文件源码中可以找到他的调用


04
实现流程
所以整个流程是这样的:
Class.forName函数加载了驱动类文件,在驱动类文件中引用了DriverManager类,驱动类文件在类被加载的时候调用了静态代码块中的内容,其中就有DriverManager的registerDriver方法,通过这个方法将自己的实例化对象放到了DriverManager的registeredDrivers列表里面,然后当主函数中调用
DriverManager.getConnection()方法的时候就可以从registeredDrivers列表中获取到相应的对象来建立连接了。
虽然了解上述原理并且读了源代码,但是仍然有疑问: 那种驱动装载方式怎么看都有点怪异,为什么要用这种方式来创建连接呢?经过查找资料发现SPI机制
SPI机制
SPI英文为 Service Provider Interface单从字面可以理解为Service提供者接口,正如从SPI的名字去理解SPI就是Service提供者接口;我对SPI的定义:提供给服务提供厂商与扩展框架功能的开发者使用的接口。
在我们日常开发的时候都是对问题进行抽象成 Api 然后就提供各种Api的实现,这些Api的实现都是封装与我们的Jar中或框架中的虽然当我们想要提供一种Api新实现时可以不修改原来代码只需实现该Api就可以提供Api的新实现,但我们还是生成新Jar或框架(虽然可以通过在代码里扫描某个目录已加载Api的新实现,但这不是Java的机制,只是hack方法),而通过Java SPI机制我们就可以在不修改Jar包或框架的时候为Api提供新实现。
很多框架都使用了java的SPI机制,如java.sql.Driver的SPI实现(MySQL驱动、oracle驱动等)、common-logging的日志接口实现、dubbo的扩展实现等等框架;
SPI机制的约定:
- 在META-INF/services/目录中创建以接口全限定名命名的文件该文件内容为Api具体实现类的全限定名
- 使用ServiceLoader类动态加载META-INF中的实现类
- 如SPI的实现类为Jar则需要放在主程序classPath中
- Api具体实现类必须有一个不带参数的构造方法
综上所述,这么做的原因:
- 应用SPI机制,起到解耦合的作用,不同数据库厂商的驱动实现遵循同一个接口规范,可以自由切换
- 反射的性能比较低,在能实现相同目标表现下能不用尽量不用
- 当时的设计人员在设计上存在一定的缺陷,进而产生这种怪异的创建连接方式,因此Class.forName这种方式在java 1.6之后就可以省略不写了。
05
后话
Java1.6版本发布于2006年4月在那之后直到现在,接触到的绝大部分教材和资料在讲述JDBC这部分时候仍然是使用老的连接方式,或者没有说明不同JDK版本之间的区别,这无论是给学习还是生产使用都造成了困扰,这也给广大编程人员以提示:
在日常工作中,要具备对技术的敏感性和对问题的探究精神,遇到不明白的问题或者不合理的地方,在不影响当前工作的前提下应该尽量利用各种资源把不清楚的地方弄明白并且做好记录,作为技术的积累和沉淀。
虽然现在多数情况下应用的是更高级的连接池和持久化框架,这些框架都对数据库连接进行了很好的封装,日常开发中直接应用JDBC连接的情况相对来说比较少,但是JDBC方式的高性能和高可优化性对性能要求较高的场景下依然是一种比较好的选择,所以还是建议给予足够的重视。