1. 程式人生 > 實用技巧 >JDBC【4】-- SPI底層原理解析

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);
    }

查詢載入類的迭代器,到底是什麼?從名字來看,是一個懶載入器,就是延遲載入,從名字來看,大概能猜到,這個就是使用的時候才載入,真的是這樣麼???接著看下去:

上面