个人上一篇博客类加载器与双亲委派中提到,SPI机制是一种上级类加载器调用下级类加载器的情形,所以会打破类加载的双亲委派模型。为了深刻理解其中的细节,本博客详细剖析一下SPI机制,并以JDBC为例,基于源码来进行分析。html
SPI(Service Provider Interface),是JDK内置的服务提供发现机制。即JDK内部定义规范的接口,不一样厂商基于标准服务接口实现具体的实现类和方法。SPI通常被用来作框架扩展的开发。
下面这张图,很简明扼要地阐释了SPI的机理。
与SPI相对应的,是咱们耳熟能详的API。API不须要上图中“标准服务接口”这一环节,而是调用方直接调用服务提供方。按照上一篇博客的分析,“标准服务接口”位于Java核心类库中,使用boot类加载器进行加载,而boot类加载器是没法获取“第三方实现类”的位置的。因此,相较于API而言,SPI须要打破双亲委派模型。java
可是,我陷入思考,SPI这样的模式有什么好处吗,或者说API有什么缺点吗?mysql
想象一下,若是程序直接调用第三方类库,当第三方类库发生改动时,应用程序代码极可能须要随之改动。但若是在JDK内部定义标准服务接口,要求第三方厂商实现这些接口,那不管实现类如何改动,只要标准接口不变,都不会影响到应用程序。因此我认为SPI机制的根本目的是为了“解耦”。这也就是面向对象中所谓的“接口编程”,把装配的控制权移到程序以外。sql
许多著名的第三方类库都采纳了SPI机制,JDBC就是其中之一。数据库厂商会基于标准接口来开发相应的链接库。如MySQL何PostgreSql的驱动都实现了标准接口:java.sql.Driver。对于应用程序而言,无需关心是MySQL仍是PostgreSql,只须要与标准服务接口打交道便可。SPI正是基于这种模式完成了解耦合。数据库
固然,即使如此,SPI依旧是存在缺点和不足的,以下:编程
首先来看一段使用JDBC的简单代码:安全
@Test public void testJDBC() throws SQLException, ClassNotFoundException { String url = "jdbc:mysql://localhost:3307/mls"; String userName = "root"; String password = "123456"; // Class.forName("com.mysql.cj.jdbc.Driver"); Connection con = DriverManager.getConnection(url, userName, password); Statement statement = con.createStatement(); String sql = "select * from mlsdb where id=1"; ResultSet rs = statement.executeQuery(sql); while (rs.next()) { System.out.println(rs.getString("province")); } }
注意到中间有一行注释的代码Class.forName("com.mysql.cj.jdbc.Driver");
,其实这一行可写可不写。app
个人倒数第二篇博客类加载时机与过程里提到,Class.forName方法会触发“初始化”,即触发类加载的进行。所以若是写上这行代码,此处则是使用APP类加载器加载mysql的jdbc驱动类。框架
然而,这一句Class.forName不用写,代码也能正常运行。由于加载DriverManager类时,会将MySQL的Driver对象注册进DriverManager中。具体流程后文会细说。其实这就是SPI思想的一个典型的实现。得益于SPI思想,应用程序中无需指定相似"com.mysql.cj.jdbc.Driver"这种全类名,尽量地将第三方驱动从应用程序中解耦出来。ide
下面,经过源码来分析驱动加载以及服务发现的过程,主要涉及到DriverManager和ServiceLoader两个类
DriverManager是用于管理Jdbc驱动的基础服务类,位于Java.sql包中,所以是由boot类加载器来进行加载。加载该类时,会执行以下代码块:
/** * Load the initial JDBC drivers by checking the System property * jdbc.properties and then use the {@code ServiceLoader} mechanism */ static { loadInitialDrivers(); println("JDBC DriverManager initialized"); }
上述静态代码块会执行loadInitialDrivers()方法,该方法用于加载各个数据库驱动。代码以下:
private static void loadInitialDrivers() { String drivers; try { drivers = AccessController.doPrivileged(new PrivilegedAction<String>() { public String run() { return System.getProperty("jdbc.drivers"); } }); } catch (Exception ex) { drivers = null; } AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);//实例化ServiceLoader对象,并注入线程上下文类加载器和Driver.class Iterator<Driver> 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.load(Driver.class)
此方法会把实例化一个ServiceLoader对象,而且向其注入线程上下文类加载器和Driver.class;loadedDrivers.iterator()
:得到ServiceLoader对象的迭代器;driversIterator.hasNext()
:查找Driver类;driversIterator.next()
:在实现的“next()”方法中进行类加载,使用上面的线程上下文类加载器。ServiceLoader.load(Driver.class);
的代码及相关调用方法以下:
public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader();// 得到线程上下文类加载器 return ServiceLoader.load(service, cl); } public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); } public void reload() { providers.clear(); lookupIterator = new LazyIterator(service, loader); } private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); }
通过上述过程,用成员变量private final ClassLoader loader;
引用传入的类加载器,用service接收Driver.class。同时,上述过程当中实例化了一个LazyIterator对象,并用成员变量lookupIterator来引用。
执行ServiceLoader的“hasNext()”方法时最终会调用lookupIterator迭代器的“hasNext()”方法(此处暂且省略调用过程),以下:
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; } public boolean hasNext() { if (acc == null) { return hasNextService(); } else { PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { public Boolean run() { return hasNextService(); } }; return AccessController.doPrivileged(action, acc); } }
上述过程经过configs = loader.getResources(fullName)
来查找实现Driver接口的类。
一样,ServiceLoader的迭代器的“next()”方法最终会调用lookupIterator迭代器的“next()”方法,以下:
private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { c = Class.forName(cn, false, loader);//使用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 S next() { if (acc == null) { return nextService(); } else { PrivilegedAction<S> action = new PrivilegedAction<S>() { public S run() { return nextService(); } }; return AccessController.doPrivileged(action, acc); } }
能够看到,next()会最终调用到nextService()方法,并在此方法中经过c = Class.forName(cn, false, loader);
执行类加载。此处的loader也是由ServiceLoader中的loader传入的,即为前文提到的线程上下文类加载器。
经历了上述ServiceLoader类中一系列操做以后(包括服务发现和类加载),位于mysql驱动包中的Driver类会被初始化。该类以下所示
package com.mysql.cj.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!"); } } }
上述Driver类加载时,会执行静态代码块,即执行DriverManager.registerDriver(new Driver());
方法向DriverManager中注册一个Driver实例。
咱们再回到DriverManager类中,看看registerDriver方法:
public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException { /* Register the driver if it has not already been added to our list */ if(driver != null) { registeredDrivers.addIfAbsent(new DriverInfo(driver, da)); } else { // This is for compatibility with the original DriverManager throw new NullPointerException(); } println("registerDriver: " + driver); }
会将该MySQL驱动添加到成员变量registeredDrivers中,该成员变量存放已注册的jdbc驱动列表,以下:
// List of registered JDBC drivers private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
如此一来,服务发现、类加载、驱动注册便到此结束。接下来,应用程序执行数据库链接操做时,会调用“getConnection”方法,遍历registeredDrivers,获取驱动,创建数据库链接。
以上即是JDBC的SPI机制总结,最核心的地方在于,ServiceLoader中使用低级别的加载器发现Driver类,并进行类加载。这些工做是BootStrap类加载器所办不到的。因为DriverManager和ServiceLoader都位于Java核心类库中,使用BootStrap类加载器来加载,因此须要经过线程上下文类加载器向ServiceLoader对象中传入一个低级别的类加载器,如系统类加载器,从而来打破双亲委派机制。