JDBC【4】-- SPI底層原理解析
前面已經講過SPI的基本實現原理了,demo也基本實現了,再來說說SPI。
http://aphysia.cn/archives/jdbcspi
背景:SPI是什麼?
SPI
,即是Service Provider Interface
,是一種服務提供(介面實現)發現機制,可以通過ClassPath路徑下的META-INF/Service
檔案查詢檔案,載入裡面定義的類。
一般可以用來啟用框架拓展和替換元件,比如在最常見的資料庫連線JDBC中,java.sql.Driver
,不同的資料庫產商可以對介面做不一樣的實現,但是JDK怎麼知道別人有哪些實現呢?這就需要SPI
,可以查詢到介面的實現,對其進行操作。
用兩個字解釋:解耦
再簡單點說?
就是Java核心包不知道第三方的包會怎麼實現一個介面,定義了一個規則:你要對這個類拓展,那你就把你的實現類配置到一個檔案裡面,檔名就是你要拓展的介面,這樣子,我只要用ServiceLoader
載入介面,我就可以獲取到實現類的例項。
對於java核心包來說,我不知道你要怎麼實現介面,但是隻要你按我說的做,配置好,我就能保證你只要引入你自己的包,我就可以執行到你的程式碼。
核心程式碼如下:
ServiceLoader<DBConnectionService> serviceLoader= ServiceLoader.load(DBConnectionService.class);
所以我們此時假設自己對ServiceLoader
已經十分好奇了,這是什麼?這是怎麼實現的?這麼牛逼?
那就看原始碼?夜深人靜剛剛好,白天也看不下去。
這裡需要注意的是,這個ServiceLoader
是一個泛型類,實現了Iterable
,說明了什麼?說明它的功能有一部分和集合是差不多的,可以將多個服務的實現類載入在裡面!!!可以通過遍歷的方式,一一取出來
先看看ServiceLoader
的類成員介面,不急著看load()
函式:
public final class ServiceLoader<S> implements Iterable<S> { // 讀取配置檔案的路徑 private static final String PREFIX = "META-INF/services/"; // 載入的服務類或者介面的實現類 private final Class<S> service; // 類載入器 private final ClassLoader loader; // 訪問控制器 private final AccessControlContext acc; // 已載入的服務類集合 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 內部類,真正載入服務類的迭代器 private LazyIterator lookupIterator; ... }
現在來看load()
函式,其實裡面呼叫的也還是serviceLoader
本身的構造器,兩個load方法
- 一個只需要傳入需要實現的服務介面
service
- 另一個則是需要同時傳入類載入器
loader
// 當前執行緒的類載入器作為預設載入器
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);
}
我們還是來看serviceLoader
的構造器:
private ServiceLoader(Class<S> svc, ClassLoader cl) {
// 要載入的介面
service = Objects.requireNonNull(svc, "Service interface cannot be null");
// 載入器,如果為null則預設使用系統載入器進行載入
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
// 控制訪問器
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
// 重新載入
reload();
}
看重新載入的方法:
public void reload() {
// 清空已經載入的服務類
providers.clear();
// 初始化查詢載入類的迭代器
lookupIterator = new LazyIterator(service, loader);
}
查詢載入類的迭代器,到底是什麼?從名字來看,是一個懶載入器,就是延遲載入,從名字來看,大概能猜到,這個就是使用的時候才載入,真的是這樣麼???接著看下去:
上面