Java SPI & JDBC相关源码解析

Java SPI的具体约定为:
当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader
应用:common-logging、jdbc4

用JDBC做例子:
JDBC4之前,需要先Class.forName("com.mysql.jdbc.Driver"); 加载驱动
驱动的加载过程,实际上是通过反射加载com.mysql.jdbc.Driver类,在类静态块中注册驱动:

static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } }

【Java SPI & JDBC相关源码解析】注册过程就是在DriverManager的CopyOnWriteArrayList列表中添加一个DriverInfo实例

在调用驱动获取连接(Connection conn=DriverManager.getConnection(url,user,password); )时,实质是遍历这个CopyOnWriteArrayList,尝试用所有已注册的驱动创建连接,并返回第一个非null的连接:
for (DriverInfo aDriver : registeredDrivers) { if (isDriverAllowed(aDriver.driver, callerCL)) { try { println("trying " + aDriver.driver.getClass().getName()); Connection con = aDriver.driver.connect(url, info); if (con != null) { println("getConnection returning " + aDriver.driver.getClass().getName()); return (con); } } catch (SQLException ex) { if (reason == null) { reason = ex; } } } else { println("skipping: " + aDriver.getClass().getName()); } }

JDBC4之后:
不需要再显式通过Class.forName注册驱动,原因:
DriverManager在静态块通过SPI完成了注册过程:
static { loadInitialDrivers(); println("JDBC DriverManager initialized"); } private static void loadInitialDrivers() { …… AccessController.doPrivileged(new PrivilegedAction() { public Void run() { ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class); Iterator driversIterator = loadedDrivers.iterator(); while(driversIterator.hasNext()) { driversIterator.next(); } …… } …… } }

load方法实际上是调用当前线程类加载器或根加载器加载java.sql.Driver.class创建了一个ServiceLoader实例
在ServiceLoader构造方法中,又通过reload方法创建了一个内部实现的懒加载迭代器LazyIterator的实例
之后通过loadedDrivers.iterator()获取该实例,在遍历方法next()中,完成com.mysql.jdbc.Driver类的加载:
next()方法调用了nextService()方法:
private S nextService() { …… Class c = null; try { c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service,"Provider " + cn + " not found"); } …… }

之所以是限定在/META-INF/services目录,是因为LazyIterator的hasNextServices方法在每个Name前面都添加了这个前缀:
String fullName = PREFIX + service.getName();


自定义实现时可以修改,如Dubbo框架就限定在 /META-INF/dubbo/internal、/META-INF/dubbo和/META-INF/services目录下:
类名:ExtensionLoader
private Map> loadExtensionClasses() { …… this.loadFile(extensionClasses, "META-INF/dubbo/internal/"); this.loadFile(extensionClasses, "META-INF/dubbo/"); this.loadFile(extensionClasses, "META-INF/services/"); return extensionClasses; }

测试:
在 try-catch 块后面一句打断点,debug运行:
Java SPI & JDBC相关源码解析
文章图片

查看mysql-connector的META-INF/services/:
Java SPI & JDBC相关源码解析
文章图片

之所以加载了第一个而不是第二个,是因为测试的时候,使用的url是 jdbc:mysql:// 开头,com.mysql.jdbc.Driver已经可以返回一个非空连接。
即使会尝试让所有驱动都连接一遍:
在FabricMySQLDriver.class的connect方法,可以看到:
return !url.startsWith("jdbc:mysql:fabric://") ? null : super.parseURL(url.replaceAll("fabric:", ""), defaults);

即:测试用的url会让FabricMySQLDriver创建一个null连接,从而被getConnection方法pass掉

    推荐阅读