JDK源碼分析之細說SPI機制之實現原理剖析

一言不合就貼概念:java

SPI 全稱爲 (Service Provider Interface) ,是JDK內置的一種服務提供發現機制。 目前有很多框架用它來作服務的擴展發現, 簡單來講,它就是一種動態替換髮現的機制, 舉個例子來講, 有個接口,想運行時動態的給它添加實現,你只須要添加一個實現緩存

是否是說了這麼多還不知道他到底是幹什麼的?我也是。。安全

不過給個人理解是,我只須要接口就夠了,實現類我不去管,也不須要new,有點相似於IOC的模式,至於適用場景嘛,你們能夠本身YY一下。。app

這時候就會有人問,有了SPI就不須要寫實現類了嗎?錯。爲何呢,且聽我一一道來。框架

 首先引用一張圖ide

 

""

那麼能夠看得出,首先咱們須要在Classpath中有一個叫META-INF/services的文件夾,至於爲何是這個,以後在源碼中會體現出來,暫時賣個關子測試

第二步就是建立接口類。ui

好比咱們弄一個叫DemoService的類。spa

/**
 * SPI Test
 * @author Autorun
 * Created by Autorun on 2018/1/26.
 */
public interface DemoService {

    void sayHello();
}

第三步就是建立該service對應的實現類。code

/**
 * SPI Test impl
 * @author Autorun
 * Created by Autorun on 2018/1/26.
 */
public class DemoServiceImpl implements DemoService {
    @Override
    public void sayHello() {
        System.out.println("hello SPI!!!!");
    }
}

第四步:咱們須要在 META-INF/services 中建立對應的映射文件(暫且叫映射文件吧), 我以爲也比較形象

文件名稱與接口類的全類名保持一致

我這裏放到了 org.jdk.demo.java.util.spi.DemoService 這個下面,因此對應的文件名應該是 org.jdk.demo.java.util.spi.DemoService 

而後將實現類的路徑配置到該文件中。

我這裏對這個接口有2個實現類。那麼能夠寫兩個(原則上應該是能夠寫無數個的)

而後咱們寫一個對應的測試類,用於測試咱們寫的方法

/**
 * @author Autorun
 * Created by Autorun on 2018/1/26.
 */
public class DemoServiceTest {

    public static void main(String[] args) {
        ServiceLoader<DemoService> services = ServiceLoader.load(DemoService.class);
        Iterator<DemoService> it = services.iterator();
        while (it.hasNext()) {
            DemoService service = it.next();
            service.sayHello();
        }
    }
}

在這裏。咱們測試一下。

 

會發現。他把咱們實現的方法所有都調用了一遍。經過迭代器中調用sayHello方法

先貼屬性

咱們看到了META-INF/services/ 因此,呵呵,知道爲何必須寫在這個目錄下了吧?

而後咱們開始看看ServiceLoader.load(DemoService.class)這一步他到底作了什麼,它是如何將咱們的每個實現類的方法執行起來的 

public static <S> ServiceLoader<S> load(Class<S> service) {
        // 獲取上下文的類加載器。而後調用重載的方法(ServiceClass, ClassLoader)
        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);
    }
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();
    }

能夠看得出來,他初始化了ServiceLoader這個對象,並將類加載器和接口類賦值給私有變量並調用了reload方法。咱們看看reload方法作了什麼

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

它又建立了一個內部類,叫lazyIterator,經過名字咱們能夠看得出他是一個迭代器對象的實現類。順便說一嘴,這個類纔是真正去調用SPI類的地方,咱們從上面本身寫的測試代碼中應該就明白了。

廢話很少說,繼續貼代碼。。

咱們while中調用了hasNext方法。咱們看看它內部作了什麼

public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

第一次進來的時候acc確定是空的,由於沒有看到有給它賦值的地方。

它調用了內部的hasNextService方法。

private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    // meta-inf/services/xxx.DemoService 全名稱
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        // 實用類加載器加載classpath下的文件。
                        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;
        }
// 解析文件細節
private Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError
    {
        InputStream in = null;
        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();
    }

繼續看獲取next遊標的元素

public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }
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 中
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

那麼能夠得出,他會把該文件中全部的行都讀到,而且初始化,放入providers 的集合中。而後將該實例返回回去。

咱們自行調用sayHello方法。那就執行了對應的實現類代碼啦

說到這裏,咱們應該很清晰的對JDK本身提供的SPI機制有了詳細的認知。那麼,他有沒有什麼問題呢?

答案是確定的,

1:懶加載,這個沒毛病。可是隻要一調用next方法,就會把全部的實現類都加載進來。對咱們來說是不科學的。這也不是咱們須要的,由於在特定場合咱們只須要指定的類就夠了,而不須要那麼多實現類。

2:內部緩存沒對外開放,也能夠理解,安全。那代價就是咱們須要本身去將具體的實現類放入咱們的容器中。

 

總結: 

其實他的SPI作了以下事情:

1.根據對應的接口類去找META-INF/services/下的接口類名

2:經過service獲取對應的迭代器,調用hasNext方法去將META-INF/services/下對應全限定名文件中的類解析出來。

3:調用next方法加載類,並將其緩存到provides的map中。

並將其對應的實現類返回

 

小做業(留給有心的人): 自行實現一套SPI機制,實現根據指定類去加載對應的實現類。並將其緩存起來。

最後歡迎你們加入 JAVA那些事 77174608 一塊兒成長進步。。

相關文章
相關標籤/搜索