揭秘JDK SPI

SPI的全稱是Service Provider Interface,它是為了實(shí)現(xiàn)在模塊裝配的時候能不在程序里動態(tài)指明,這就需要一種服務(wù)發(fā)現(xiàn)機(jī)制。 JAVA SPI就是提供這樣的一個機(jī)制,為某個接口尋找服務(wù)實(shí)現(xiàn)的機(jī)制。
我們舉一個小例子加以說明
首先我有一個接口IHello.java

public interface IHello {
    void sayHello();
}

有兩個接口的實(shí)現(xiàn)類HelloImpl1,HelloImpl2

public class HelloImpl1 implements IHello {
    @Override
    public void sayHello() {
        System.out.println("我是Impl1");
    }
}

public class HelloImpl2 implements IHello {
    @Override
    public void sayHello() {
        System.out.println("我是Impl2");
    }
}

在MATE-INF下面的services文件夾中有一個以接口的全限定名命名的文件,里面的內(nèi)容就是實(shí)現(xiàn)類的全限定名。
使用時使用幫助類ServiceLoader,下面是我們具體的使用方法

    public static void main(String[] args){
        ServiceLoader<IHello> s = ServiceLoader.load(IHello.class);
        Iterator<IHello> iHelloIterator = s.iterator();
        while (iHelloIterator.hasNext()) {
            IHello iHello = iHelloIterator.next();
            iHello.sayHello();
        }
    }

我們大體解釋一下,搜索IHello類型的實(shí)例,之后遍歷執(zhí)行,我們并沒有指明是具體的哪個IHello的實(shí)現(xiàn)類進(jìn)行操作,那么是誰指定的呢?就是META-INF/services 下面的那個文件內(nèi)容決定的,如果內(nèi)容是:

com.test.demo.impl.HelloImpl2

那么就由Impl2執(zhí)行,如果是

com.test.demo.impl.HelloImpl1
com.test.demo.impl.HelloImpl2

就是兩個都執(zhí)行。在我們不需要改動代碼的情況下可以動態(tài)的切換執(zhí)行的實(shí)現(xiàn)類。
ServiceLoader是怎樣執(zhí)行的呢?
首先創(chuàng)建這個類型的ServiceLoader

    public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

當(dāng)需要的屬性都準(zhǔn)備完畢,開始reload

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

關(guān)注這個LazyIterator的操作,查詢實(shí)現(xiàn)類都是通過這個類進(jìn)行的。

    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

這個方法是LazyIterator判斷有沒有下一個服務(wù)的方法,我們可以先看一下
這個fullName就是前綴(META-INF/services/)加上要搜索服務(wù)的name組成,所以我們的文件的路徑及名稱都已經(jīng)確定了,只能這么寫,否則搜索不到,我們再來看一下parse方法,就是解析文件中的內(nèi)容放到Iterator中

private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

下面是獲取service的過程,經(jīng)過上一步,基本上都解析出來實(shí)現(xiàn)類的名稱了,所以要通過Class.forName反射加載這個類的Class對象,然后實(shí)例化(newInstance),最后返回。

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

上面就是這個ServiceLoader執(zhí)行的流程。這個SPI大多數(shù)開發(fā)人員可能不熟悉,因?yàn)檫@個是針對廠商或者插件的,現(xiàn)在很多軟件都已采用或者擴(kuò)展了這種。比如說dubbo,jdbc等,還是有必要了解一下的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,695評論 19 139
  • SPI框架實(shí)現(xiàn)之旅二:整體設(shè)計(jì) 上一篇簡單的說了一下spi相關(guān)的東西, 接下來我們準(zhǔn)備開動,本篇博文主要集中在一些...
    一灰灰blog閱讀 1,591評論 0 5
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,288評論 6 342
  • 烏云一朵朵掩蓋了蔚藍(lán)的天空,微涼的風(fēng)吹起發(fā)絲。沒一會兒,雨便噼里啪啦的砸下來,傳來一陣泥土自然的清香。 夏果連忙撐...
    汐夏夏閱讀 223評論 5 1
  • 老實(shí),聽話,真干!六字真言中,老實(shí)放在第一位。老實(shí)就是要遵守規(guī)則,心無雜念!但是現(xiàn)在的學(xué)校一不小心就培養(yǎng)出了...
    伏羲師范熊芳閱讀 614評論 0 3

友情鏈接更多精彩內(nèi)容