前言
雖然我自己在前段時間再總結一些Java知識,但是經過最近的面試發現,很多自己掌握的並不牢靠,所以決定把原來很多內容拆分出來一部分一部分自己寫,這篇主要在梳理一遍Java的SPI 機制吧。溫故而知新,可以爲師矣。
介紹
Java SPI 全程爲 Service Provider Interface,直譯過來就是 服務提供商接口。我理解的概念的話就是,由JDK語言開發組制定一系列功能接口,但功能的具體實現是由各個服務商自行提供。這也滿足的依賴倒置原則。依賴倒置原則的核心就是要我們面向接口編程,理解了面向接口編程。
具體事例
最熟悉的SPI服務應該就是JDBC了
在java.util.sql中定義了對於數據庫功能的各個接口,以及數據庫操作生命週期中各對象的接口
如Driver表示數據庫的驅動器,Connection表示一次數據庫的連接
我們在使用第三方實現的時候我們一般是直接通過java.util.sql中的DriverManager來獲取具體的第三方Driver或者Connection,那麼DriverManager是如何加載到第三方數據庫的呢?
接下來我們就好好梳理一下從接口Class文件加載到第三方實現的加載,以及第三方實現的調用的完整流程,看一下DriverManager的源碼
環境
jdk1.8.0_144
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
加載SPI第三方實現
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
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);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
//.... 此處省略一些不重要的內容
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
//關鍵是這裏的Class.forName(aDriver,true,ClassLoader.getSystemClassLoader())
//此處使用ClassLoader.getSystemClassLoader()直接使用了AppClassLoader來加載具體實現類,打破了雙親委派機制
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
再看一下第三方的Driver實現類中都幹了些什麼,此處以com.mysql.jdbc的Driver爲例
static {
try {
//將自己的Driver實例註冊進java.util.sql.DriverManager的CopyOnWriteArrayList列表中,供後期使用
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
當SPI接口調用第三方的功能實現,此處以JDBC的getConnection爲例
try {
DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test","test","123456");
} catch (SQLException e) {
e.printStackTrace();
}
@CallerSensitive
public static Connection getConnection(String url)
throws SQLException {
java.util.Properties info = new java.util.Properties();
//此處返回的是直接調用DriverManager的類,一般爲我們自己的程序類
return (getConnection(url, info, Reflection.getCallerClass()));
}
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
//此處爲我們自己程序類的類加載器 一般爲AppClassLoader
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
//此處省略一些不重要的內容
for(DriverInfo aDriver : registeredDrivers) {
//由AppClassLoader檢查是否已經裝載了第三方驅動類
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
//此處省略一些不重要的內容
}
總結
到目前爲止我們已經弄清楚,由BootstrapClassloader加載的協議接口類,如何打破雙親委派機制 來加載AppClassLoader纔可以加載到的classpath中的第三方實現類
主要方式爲
-
使用ClassLoader.getSystemClassLoader來獲取AppClassLoader
Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());
-
雖然實際使用中沒有用到,使用Thread.currentThread().getContextClassLoader()獲取AppClassLoader
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}