個人上一篇博客類加載器與雙親委派中提到,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對象中傳入一個低級別的類加載器,如系統類加載器,從而來打破雙親委派機制。