JDBC【4】-- SPI底層原理解析

前面已經講過SPI的基本實現原理了,demo也基本實現了,再來講說SPI。java

http://aphysia.cn/archives/jd...sql

背景:SPI是什麼?
SPI,便是Service Provider Interface,是一種服務提供(接口實現)發現機制,能夠經過ClassPath路徑下的META-INF/Service文件查找文件,加載裏面定義的類。
通常能夠用來啓用框架拓展和替換組件,好比在最多見的數據庫鏈接JDBC中,java.sql.Driver,不一樣的數據庫產商能夠對接口作不同的實現,可是JDK怎麼知作別人有哪些實現呢?這就須要SPI,能夠查找到接口的實現,對其進行操做。
用兩個字解釋:解耦數據庫

再簡單點說?
就是Java核心包不知道第三方的包會怎麼實現一個接口,定義了一個規則:你要對這個類拓展,那你就把你的實現類配置到一個文件裏面,文件名就是你要拓展的接口,這樣子,我只要用ServiceLoader加載接口,我就能夠獲取到實現類的實例。app

對於java核心包來講,我不知道你要怎麼實現接口,可是隻要你按我說的作,配置好,我就能保證你只要引入你本身的包,我就能夠運行到你的代碼。
框架

核心代碼以下:ide

ServiceLoader<DBConnectionService>  serviceLoader= ServiceLoader.load(DBConnectionService.class);

因此咱們此時假設本身對ServiceLoader已經十分好奇了,這是什麼?這是怎麼實現的?這麼牛逼?函數

那就看源碼?夜深人靜剛恰好,白天也看不下去。學習

這裏須要注意的是,這個ServiceLoader是一個泛型類,實現了Iterable,說明了什麼?說明它的功能有一部分和集合是差很少的,能夠將多個服務的實現類加載在裏面!!!能夠經過遍歷的方式,一一取出來ui

先看看ServiceLoader的類成員接口,不急着看load()函數:this

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

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

上面 👆 咱們說到ServiceLoader實際上是一個泛型類,實現了Iterator接口,說明它能夠被遍歷,遍歷的元素是什麼呢?就是上面所說的成員變量LinkedHashMap<String,S> providers,實現的服務都加載在裏面了。

那咱們就看看遍歷的時候怎麼取的?
這就涉及到了Iterable接口的方法foreach()了,咱們就來看看。
總所周知,Iterable接口的方法以下:

Iterator<T> iterator();
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }

那我猜遍歷的方法應該被ServiceLoader實現的時候,已經重寫了,果不其然:
看獲取Iterator的方法實現,咱們能夠發現一個驚天㊙️密:也就是其實咱們遍歷的時候,優先是使用已知的集合迭代器,這個集合,就是存儲咱們的服務提供者的集合,也就是已經加載的服務類集合。若是這個集合已經遍歷完成的時候,就會調用查找迭代器去查找,無論next()仍是hasNext()方法,都是這樣的。
同時已經加載的服務,是不能夠被移除的,爲了防止這一點,在移除的時候會返回異常。

public Iterator<S> iterator() {
        return new Iterator<S>() {

            //  被查找到的已知的服務提供者的迭代器
            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            // 若是已知的迭代器,也就是存儲服務提供者的集合迭代器,若是這個有下一個元素,那麼就會直接返回true,若是沒有那麼就會調用查找迭代器的hasNext()
            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                // 若是存儲已知的服務提供者的迭代器還有下一個元素,那麼直接取出來直接返回便可,不然就用查找迭代器去查找下一個
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            // 不能夠移除已經加載額服務提供者
            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

Iterator的其餘方法呢?

固然是用了默認實現了,其餘兩個方法都加了default關鍵字,ServiceLoader沒有去實現它,能夠不實現,用默認實現就能夠。

因此咱們的重點是什麼?固然是這個lookupIterator,它但是一個延遲加載器,爲何這麼說,我以爲應該和上面的分析有關,先遍歷已經加載的,而後沒有了,纔會使用這個延遲查找迭代器,從它的名字就能夠很清楚的看出來,這其實就是一個查找的迭代器,別人都是迭代遍歷已經存在的元素,它倒好,懶到必定程度了,用來查找。

廢話少說,直接看看它怎麼實現的,這麼🐂 牛!
在回頭看看前面初始化的時候,構造是這樣子的:

// 初始化查找加載類的迭代器
        lookupIterator = new LazyIterator(service, loader);

能夠看到實際上是將須要加載的服務接口以及類加載器傳遞進來了。
代碼精簡,看👇下面的代碼加註解,應該很清晰了。

private class LazyIterator
        implements Iterator<S>
    {
        // 須要加載的服務
        Class<S> service;
        // 類加載器
        ClassLoader loader;
        // 實現類的url(多個)
        Enumeration<URL> configs = null;
        // 實現類的全名(多個)
        Iterator<String> pending = null;
        // 迭代器中下一個實現類的全名
        String nextName = null;

        // 構造器其實沒有什麼操做,單純保存
        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        // 獲取下一個
        public S next() {
            // 若是控制訪問器是空的
            if (acc == null) {
                // 調用獲取下一個元素
                return nextService();
            } else {
                // 特權動做,重寫的其實也是調用nextService()
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                // 經過控制訪問器的特權訪問,跳過檢查
                return AccessController.doPrivileged(action, acc);
            }
        }
        // 是否有下一個元素
        public boolean hasNext() {
            if (acc == null) {
                // 若是訪問控制器是null,那麼久直接調用hasNextService方法
                return hasNextService();
            } else {
                // 生成特權動做
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                // 使用訪問控制器執行特權動做
                return AccessController.doPrivileged(action, acc);
            }
        }

        // 不能夠移除
        public void remove() {
            throw new UnsupportedOperationException();
        }

        // 是否有下一個元素
        private boolean hasNextService() {
            // 若是下一個元素的全類名名字不爲null,那麼確定是有下一個元素
            if (nextName != null) {
                return true;
            }
            // 若是這個實現類的url是空的,怎麼辦?加載進去
            if (configs == null) {
                try {
                    // 獲取全名
                    String fullName = PREFIX + service.getName();
                    // 若是類加載器是null
                    if (loader == null)
                        // 經過全名,拿到全名的url,這個url其實就是根據名字找到的配置文件的路徑
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        // 不然調用loader自身的方法獲取
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }

            // 若是實現類的全限定類名是空的,那就確定須要從文件裏面讀出來呀,由於文件名是接口,裏面配置的是接口的實現類,pending保存的就是實現類
            while ((pending == null) || !pending.hasNext()) {
                // 若是須要讀取的配置沒有要讀的了
                if (!configs.hasMoreElements()) {
                    // 直接返回false
                    return false;
                }
                // 不然須要解析配置文件裏面的內容
                pending = parse(service, configs.nextElement());
            }
            // 下一個service的名字就更新爲讀取到的接口實現類,若是文件裏面什麼都沒有配置,那就頗有多是空的。
            nextName = pending.next();
            return true;
        }

        // 獲取下一個接口實現類,也就是服務
        private S nextService() {
            // 若是沒有下一個服務,會拋異常
            if (!hasNextService())
                throw new NoSuchElementException();
            // 下一個實現類的名稱,保存起來
            String cn = nextName;
            // 置空
            nextName = null;
            Class<?> c = null;
            try {
                // 經過反射來構造服務對象,就是實現類
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            // 判斷服務c是否是實現來自於service,二者是否是繼承關係
            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
        }
    }

經過上面的代碼,其實咱們能夠清楚的看到,這個延遲加載器,會去讀取配置類,以及實現的的接口,將實現類全類名放到configs,而後經過反射的形式構建實例對象,實例化以後才放到providers中,而後返回實現類對象。

值得注意的是,若是訪問控制器是空的,那麼就會調用特權執行:AccessController.doPrivileged(action, acc);,獲取到服務實現的時候,也會判斷是否是實現來自於咱們須要實現的接口,不然會報錯,調用的是service.isAssignableFrom(c)

上面還有一段解析配置的代碼沒有說明,補上:

private Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError
    {
        // 輸入流
        InputStream in = null;
        // buffer讀入
        BufferedReader r = null;
        ArrayList<String> names = new ArrayList<>();
        try {
            in = u.openStream();
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            // 按照每一行來讀入
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        } catch (IOException x) {
            fail(service, "Error reading configuration file", x);
        } finally {
            try {
                if (r != null) r.close();
                if (in != null) in.close();
            } catch (IOException y) {
                fail(service, "Error closing configuration file", y);
            }
        }
        // 返回的實際上是實現類全限定名的集合迭代器
        return names.iterator();
    }

    private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
                          List<String> names)
        throws IOException, ServiceConfigurationError
    {
        String ln = r.readLine();
        if (ln == null) {
            return -1;
        }
        // 對於#號後面的內容不加載,直接忽略掉
        int ci = ln.indexOf('#');
        if (ci >= 0) ln = ln.substring(0, ci);
        ln = ln.trim();
        int n = ln.length();
        if (n != 0) {
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                fail(service, u, lc, "Illegal configuration-file syntax");
            int cp = ln.codePointAt(0);
            if (!Character.isJavaIdentifierStart(cp))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service, u, lc, "Illegal provider-class name: " + ln);
            }
            // 沒有加載過纔會添加進去
            if (!providers.containsKey(ln) && !names.contains(ln))
                names.add(ln);
        }
        return lc + 1;
    }

到這裏,serviceLoader的內容就解讀完畢了,思想挺好的,有兩個迭代器,一個是提供服務的集合自己的迭代器,迭代完成以後,纔會去使用延遲調用lookup迭代器,觸發尋找操做,若是查找了,那麼就加載到集合中,下次就不用再找了。

查找的時候,直接根據該路徑下的文件,文件名就是接口,接口裏面每一行都是接口的實現類。

實例化接口實現類的時候,實際上是使用了反射,而後判斷類型以後,類型轉換成爲⤴️上轉型,放到已經發現的服務集合中,返回。

【做者簡介】
秦懷,公衆號【秦懷雜貨店】做者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。這個世界但願一切都很快,更快,可是我但願本身能走好每一步,寫好每一篇文章,期待和大家一塊兒交流。

此文章僅表明本身(本菜鳥)學習積累記錄,或者學習筆記,若有侵權,請聯繫做者覈實刪除。人無完人,文章也同樣,文筆稚嫩,在下不才,勿噴,若是有錯誤之處,還望指出,感激涕零~

相關文章
相關標籤/搜索