前言
雖然我自己在前段時間再總結(jié)一些Java知識寻拂,但是經(jīng)過最近的面試發(fā)現(xiàn)晨川,很多自己掌握的并不牢靠刷喜,所以決定把原來很多內(nèi)容拆分出來一部分一部分自己寫荣回,這篇主要在梳理一遍Java的SPI 機制吧侄榴。溫故而知新雹锣,可以為師矣。
介紹
Java SPI 全程為 Service Provider Interface癞蚕,直譯過來就是 服務(wù)提供商接口蕊爵。我理解的概念的話就是,由JDK語言開發(fā)組制定一系列功能接口桦山,但功能的具體實現(xiàn)是由各個服務(wù)商自行提供攒射。這也滿足的依賴倒置原則。依賴倒置原則的核心就是要我們面向接口編程,理解了面向接口編程恒水。
具體事例
最熟悉的SPI服務(wù)應(yīng)該就是JDBC了
在java.util.sql中定義了對于數(shù)據(jù)庫功能的各個接口会放,以及數(shù)據(jù)庫操作生命周期中各對象的接口
如Driver表示數(shù)據(jù)庫的驅(qū)動器,Connection表示一次數(shù)據(jù)庫的連接
我們在使用第三方實現(xiàn)的時候我們一般是直接通過java.util.sql中的DriverManager來獲取具體的第三方Driver或者Connection,那么DriverManager是如何加載到第三方數(shù)據(jù)庫的呢钉凌?
接下來我們就好好梳理一下從接口Class文件加載到第三方實現(xiàn)的加載咧最,以及第三方實現(xiàn)的調(diào)用的完整流程,看一下DriverManager的源碼
環(huán)境
jdk1.8.0_144
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
加載SPI第三方實現(xiàn)
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();
//.... 此處省略一些不重要的內(nèi)容
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
//關(guān)鍵是這里的Class.forName(aDriver,true,ClassLoader.getSystemClassLoader())
//此處使用ClassLoader.getSystemClassLoader()直接使用了AppClassLoader來加載具體實現(xiàn)類,打破了雙親委派機制
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
再看一下第三方的Driver實現(xiàn)類中都干了些什么,此處以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接口調(diào)用第三方的功能實現(xiàn)矢沿,此處以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();
//此處返回的是直接調(diào)用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();
}
}
//此處省略一些不重要的內(nèi)容
for(DriverInfo aDriver : registeredDrivers) {
//由AppClassLoader檢查是否已經(jīng)裝載了第三方驅(qū)動類
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());
}
}
//此處省略一些不重要的內(nèi)容
}
總結(jié)
到目前為止我們已經(jīng)弄清楚饮笛,由BootstrapClassloader加載的協(xié)議接口類咨察,如何打破雙親委派機制 來加載AppClassLoader才可以加載到的classpath中的第三方實現(xiàn)類
主要方式為
- 使用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();
}
}