Posts JDBC驱动的类加载-SPI新瓶装旧酒
Post
Cancel

JDBC驱动的类加载-SPI新瓶装旧酒

今天偶尔看JDBC驱动的问题,发现了一个有意思的事情,就是Class.forName()和SPI动态注册,原理居然是一样的,新瓶装旧酒。

老派的手工注册

JDBC很早以来一直采用驱动管理显式的注册方法,就是驱动类的使用者,显式地使用Class.forName()加载驱动类。

1
2
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con=DriverManager.getConnection(...);

Class.forName()的作用是初始化具体driver类的用static显式声明的静态初始化块。按照JDBC Driver定义的规范,每个Driver必须把自己注册到java.sql.DriverManager。比如oracle.jdbc.driver.OracleDriver源代码中的静态方法块声明类对象的一个实例,并把这个实例对象的引用提交到DriverManager.registerDriver()中。

1
2
3
4
5
6
static{
	try{
		if (defaultDriver == null){
			defaultDriver = new oracle.jdbc.OracleDriver();
			DriverManager.registerDriver(defaultDriver);
		}

注册到DriverManager中的是具体的driver的对象引用,被保存在一个容器中。

接下来DriverManager.getConnection方法会循环试探容器的一个个的driver对象引用,调用该对象的的connect()方法连接。

1
2
3
4
5
6
7
8
9
10
11
private static Connection getConnection(
	...
		for(DriverInfo aDriver : registeredDrivers) {
			if(isDriverAllowed(aDriver.driver, callerCL)) {
				try {
					Connection con = aDriver.driver.connect(url, info);
					if (con != null) {
						// Success!
						println("getConnection returning " + aDriver.driver.getClass().getName());
						return (con);
					}

Service Provider Interface

JDBC 4.0规范引入了Service Provider,指定所有的JDBC Driver jar包必须在META-INF/Services/java.sql.driver 文件中写明各自的驱动名称。开发人员不再需要Class.forName()手动加载。只需要下面一行代码就是先数据库连接。

1
Connection con=DriverManager.getConnection(...);

SPI看起来很优雅

代码太简洁到不可思议,背后它用了不少的代码,目的只是帮程序员少输入一行而已。

首先,DriverManager.getConnection是个静态方法,调用时,DriverManager中的静态初始化块必须先被执行。

1
2
3
static {
	loadInitialDrivers();
}

再调用一个静态方法,ServiceLoader去加载所有的java.sql.Driver文件声明的驱动名。马上看到了一个熟悉的身影: Class.forName(…)。

1
2
3
4
5
6
7
8
private static void loadInitialDrivers() {
	AccessController.doPrivileged(new PrivilegedAction<Void>() {
		public Void run() {
			ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);    
			...
			for (String aDriver : driversList) {
			try {
				Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());      

动态加载的好处

使用Class.forName()或者SPI的好处避免驱动类的显式import,实现了运行时的动态加载,并且隐藏了供应商的诸多细节,统一使用抽象层的JDBC API即可。

一个老问题

DriverManager.getConnection去调用具体driver对象引用的时候,是采用迭代的方式,循环地让注册列表中的每个driver试探着去连接。每次连接结果有三种:

  • 如果url格式正确,driver能连接,就返回java.sql.Connection实例
  • 如果url格式正确,但driver处理物理连接时错误,就向上抛出异常
  • 如果url格式错误,直接返回null

如果在一个应用中有多个driver驱动类被引入,于是会出现一个久远的老问题:循环连接试探的时间浪费。那么,有应用需要多个不同种类驱动的可能吗?

当然有。

好在有连接池的存在,把我们的问题掩盖了。

This post is licensed under CC BY 4.0 by the author.