1. 程式人生 > 實用技巧 >Dubbo的SPI

Dubbo的SPI

從自定義Dubbo的rpc協議來學習Dubbo的SPI

SPI 全稱為 Service Provider Interface

JDK的SPI實現

public class SpiTest {

    public static void main(String[] args) {
        // 註解1
        ServiceLoader<Human> load = ServiceLoader.load(Human.class);
        // 註解2
        Iterator<Human> iterator = load.iterator();
        // 註解3
        while (iterator.hasNext()) {
            // 註解4
            Human next = iterator.next();
            System.out.println(next.getName());
        }
    }

}
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 java.util.ServiceLoader.LazyIterator lookupIterator;

    // 私有內部類,提供對所有的service的類的載入與例項化
    private class LazyIterator implements Iterator<S> {
        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        String nextName = null;

        //...
        private boolean hasNextService() {
            if (configs == null) {
                try {
                    //獲取目錄下所有的類
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    //...
                }
                //....
            }
        }

        private S nextService() {
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                //反射載入類
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
            }
            try {
                //例項化
                S p = service.cast(c.newInstance());
                //放進快取
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                //..
            }
            //..
        }
    }
}

註解1

load方法關注最後一步關注例項化一個LazyIterator例項,賦值給變數lookupIterator

註解2

iterator方法就是例項化了一個匿名類

註解3

hasNext方法最終呼叫的就是這裡,從中我們可以看到fullName的PREFIX就是我們在resources下建立的資料夾META-INF/services/與service的全路徑名字拼接,所以我們建立的檔案是固定的,然後通過getResources獲取內容,nextElement讀取下一個

註解4

nextName在我們執行hasNext的時候已經賦上值了,這裡我們通過Class.foorName進行例項化,但是這時候先不初始化,接下來需要判斷他的繼承關係,接著通過newInstance例項化放入到providers這個快取map中

JDBC的SPI

我們看到JDBC就使用了SPI的技術,而且使用的就是JDK原生的方式

Dubbo的SPI

Jdk的SPI就是一個List,你只能通過Iterator進行遍歷獲取你想要的元素,Dubbo自定義了自己的SPI,使用的是map這種K/V結構的資料結構,這樣方便快速定位到想要的實現類。

dubbo的spi原理我覺得官網講的不錯,直接貼連結dubbo-spi原理

dubbo的spi大大提高了dubbo的靈活性,假如你對註冊中心不滿意,或者你對rmi協議想定製了,這時候你只需要自定義自己的模組即可

然後你在配置檔案中直接使用你自定義的名字即可,這裡的csrmi其實就是你在META-INF/dubbo/internal定義的檔案中的key

參考

Java SPI詳解