从dubbo到|从dubbo到 jdbc 与 spi

最近在看Dubbo源码,dubbo的功能是基于扩展点(Extension)的,如果想要修改哪个模块,可以很方便的进行扩展替换。
这种扩展点就是借鉴的spi的思想,但是dubbo并没有使用jdk原生的serviceLoader,而是自己实现了ExtensionLoader来加载扩展点,支持键值对,更为灵活,遵循的规范基本相同。这是题外话。

什么是SPI?SPI能干什么?这里有篇介绍文章--链接

最初接触到SPI的时候有些困惑,查资料发现很多文章都拿jdbc作为SPI的典型例子。
回忆当初我刚上大学的时候,hibernate、mybatis这类框架还没大火,只能自己写jdbc链接数据库的代码,是这样的

try { Class.forName("com.mysql.jdbc.Driver"); //1 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/EScore", "root", "root"); //2 pst = conn.prepareStatement("SELECT COUNT(1) FROM score"); ResultSet resultSet = pst.executeQuery(); resultSet.next(); System.out.println(resultSet.getInt(1)); } catch (Exception e) { e.printStackTrace(); }



这个Class.forName("com.mysql.jdbc.Driver")作用就是加载数据库驱动类com.mysql.jdbc.Driver,代码如下

package com.mysql.jdbc; import java.sql.DriverManager; import java.sql.SQLException; public class Driver extends NonRegisteringDriver implements java.sql.Driver { public Driver() throws SQLException { }static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } } }



加载时会先执行上面代码中的静态代码块,通过
DriverManager.registerDriver(new Driver());

com.mysql.jdbc.driver new了一个自己的实例,并注册给了DriverManager,这样DriverManager就能够使用驱动程序去获得数据库连接。
到此为止,驱动已经加载完毕,没有任何关于SPI的应用,我也是一头雾水,不明白SPI跟jdbc有啥关系。但是一想,如果jdbc使用到了spi,那么在DriverManager中一定会有相应实现,继续看DriverManager代码,有静态块如下
static { loadInitialDrivers(); println("JDBC DriverManager initialized"); }

loadInitialDrivers()方法代码如下
private static void loadInitialDrivers() { String drivers; try { drivers = AccessController.doPrivileged(new PrivilegedAction() { public String run() { return System.getProperty("jdbc.drivers"); } }); } catch (Exception ex) { drivers = null; }AccessController.doPrivileged(new PrivilegedAction() { public Void run() { ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class); Iterator driversIterator = loadedDrivers.iterator(); try{ while(driversIterator.hasNext()) { driversIterator.next(); } } catch(Throwable t) { // Do nothing } return null; } }); println("DriverManager.initialize: jdbc.drivers = " + drivers); if (drivers == null || drivers.equals("")) { return; } String[] driversList = drivers.split(":"); println("number of Drivers:" + driversList.length); for (String aDriver : driversList) { try { println("DriverManager.Initialize: loading " + aDriver); Class.forName(aDriver, true, ClassLoader.getSystemClassLoader()); } catch (Exception ex) { println("DriverManager.Initialize: load failed: " + ex); } } }

注意到这个方法里调用了ServiceLoader,来加载驱动文件

ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);


终于找到了SPI的身影,继续看会发现驱动类的加载是在遍历的时候进行的
Iterator driversIterator = loadedDrivers.iterator(); try{ while(driversIterator.hasNext()) { driversIterator.next(); } } catch(Throwable t) { // Do nothing }

loadedDrivers.iterator()返回的是
private LazyIterator lookupIterator;

LazyIterator 是ServiceLoader的内部私有类,实现了terator接口,代码如下
// Private inner class implementing fully-lazy provider lookup // private class LazyIterator implements Iterator {Class service; ClassLoader loader; Enumeration configs = null; Iterator pending = null; String nextName = null; private LazyIterator(Class service, ClassLoader loader) { this.service = service; this.loader = loader; }private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; }private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class c = null; try { c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn+ " not a subtype"); } try { S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen }public boolean hasNext() { if (acc == null) { return hasNextService(); } else { PrivilegedAction action = new PrivilegedAction() { public Boolean run() { return hasNextService(); } }; return AccessController.doPrivileged(action, acc); } }public S next() { if (acc == null) { return nextService(); } else { PrivilegedAction action = new PrivilegedAction() { public S run() { return nextService(); } }; return AccessController.doPrivileged(action, acc); } }public void remove() { throw new UnsupportedOperationException(); }}

可以看到nextService是会用Class.forName去加载驱动类,然后执行驱动类中静态块,然后DriverManager注册一个实例。。。。那么既然DriverManager使用类spi的机制去自动加载所有驱动类,我们在写代码的时候就无需再去
Class.forName("com.mysql.jdbc.Driver"); //1

是的,这行代码已经多余了,去掉之后仍然可以正常建立数据库连接,爽




【从dubbo到|从dubbo到 jdbc 与 spi】

    推荐阅读