dubbo之SPI

一、SPI簡介

SPI 全稱爲 Service Provider Interface,是一種服務發現機制。SPI 的本質是將接口實現類的全限定名配置在文件中,並由服務加載器讀取配置文件,加載實現類。這樣能夠在運行時,動態爲接口替換實現類。正所以特性,咱們能夠很容易的經過 SPI 機制爲咱們的程序提供拓展功能。SPI 機制在第三方框架中也有所應用,好比 Dubbo 就是經過 SPI 機制加載全部的組件。不過,Dubbo 並未使用 Java 原生的 SPI 機制,而是對其進行了加強,使其可以更好的知足需求。在 Dubbo 中,SPI 是一個很是重要的模塊。基於 SPI,咱們能夠很容易的對 Dubbo 進行拓展。若是你們想要學習 Dubbo 的源碼,SPI 機制務必弄懂。接下來,咱們先來了解一下 Java SPI 與 Dubbo SPI 的用法,而後再來分析 Dubbo SPI 的源碼。java

二、SPI示例

2.1 Java SPI示例

本節經過一個示例演示 Java SPI 的使用方法。首先,咱們定義一個接口,名稱爲 HelloService。apache

public interface HelloService {
    void sayHello();
}

家下來定義兩個實現類:HelloAService、HelloBService;緩存

public class HelloAService implements HelloService {
    
    @Override
    public void sayHello() {
        System.out.println("Hello, I am A");
    }
}

public class HelloBService implements HelloService {

    @Override
    public void sayHello() {
        System.out.println("Hello, I am B");
    }
}

接下來 META-INF/services 文件夾下建立一個文件,名稱爲 Robot 的全限定名 org.apache.spi.Robot。文件內容爲實現類的全限定的類名,以下:app

org.apache.spi.HelloAService
org.apache.spi.HelloBService

作好所需的準備工做,接下來編寫代碼進行測試框架

public class JavaSPITest {

    @Test
    public void sayHello() throws Exception {
        ServiceLoader<HelloService> serviceLoader = ServiceLoader.load(HelloService.class);
        System.out.println("Java SPI");
        serviceLoader.forEach(HelloService::sayHello);
    }
}

最後結果以下:ide

Java SPI
Hello, I am A
Hello, I am B

從測試結果能夠看出,咱們的兩個實現類被成功的加載,並輸出了相應的內容。關於 Java SPI 的演示先到這裏,接下來演示 Dubbo SPI。學習

2.2 Dubbo SPI

Dubbo 並未使用 Java SPI,而是從新實現了一套功能更強的 SPI 機制。Dubbo SPI 的相關邏輯被封裝在了 ExtensionLoader 類中,經過 ExtensionLoader,咱們能夠加載指定的實現類。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路徑下,配置內容以下。測試

helloAService = org.apache.spi.HelloAService
helloBService = org.apache.spi.HelloBService

與 Java SPI 實現類配置不一樣,Dubbo SPI 是經過鍵值對的方式進行配置,這樣咱們能夠按需加載指定的實現類。另外,在測試 Dubbo SPI 時,須要在 Robot 接口上標註 @SPI 註解。下面來演示 Dubbo SPI 的用法:url

public class DubboSPITest {

    @Test
    public void sayHello() throws Exception {
        ExtensionLoader<HelloService> extensionLoader = 
            ExtensionLoader.getExtensionLoader(HelloService.class);
        HelloService a = extensionLoader.getExtension("HelloAService");
        a.sayHello();
        HelloService b = extensionLoader.getExtension("HelloBService");
        b.sayHello();
    }
}

測試結果以下:.net

Java SPI
Hello, I am A
Hello, I am B

三、Dubbo SPI源碼解析

Dubbo SPI相關邏輯都在ExtensionLoader 類中,首先經過getExtensionLoader獲取一個ExtensionLoader實例,而後在根據getExtension獲取type的擴展類。
getExtensionLoader方法比較簡單,先從緩存中獲取,若是緩存不存在,則建立ExtensionLoader對象,並存入緩存中,在看getExtension方法:

public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        // 根據擴展名從緩存中獲取,緩存中沒有,建立並存入緩存
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<Object>());
            holder = cachedInstances.get(name);
        }
        Object instance = holder.get();
        // 雙重檢查
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    // 根據擴展名建立實例對象
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

上面代碼的邏輯比較簡單,首先檢查緩存,緩存未命中則建立拓展對象。下面咱們來看一下建立拓展對象的過程是怎樣的。

private T createExtension(String name) {
        // 從配置文件中加載全部的拓展類,可獲得「配置項名稱」到「配置類」的映射關係表
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    // 將當前 instance 做爲參數傳給 Wrapper 的構造方法,並經過反射建立 Wrapper 實例。
                    // 而後向 Wrapper 實例中注入依賴,最後將 Wrapper 實例再次賦值給 instance 變量
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                    type + ")  could not be instantiated: " + t.getMessage(), t);
        }
    }

咱們在經過名稱獲取拓展類以前,首先須要根據配置文件解析出拓展項名稱到拓展類的映射關係表(Map<名稱, 拓展類>),以後再根據拓展項名稱從映射關係表中取出相應的拓展類便可。相關過程的代碼分析以下

private Map<String, Class<?>> getExtensionClasses() {
        // 從緩存中獲取
        Map<String, Class<?>> classes = cachedClasses.get();
        // 雙重檢查
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // 加載全部的擴展類
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

getExtensionClasses方法一樣是先從緩存中讀取,緩存不存在,在去加載:

private Map<String, Class<?>> loadExtensionClasses() {
        // 獲取擴展類SPI註解
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if ((value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                if (names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                if (names.length == 1) {
                    cachedDefaultName = names[0];
                }
            }
        }

        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        // 加載指定文件夾下的配置文件
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    }

loadExtensionClasses 方法總共作了兩件事情,一是對 SPI 註解進行解析,二是調用 loadDirectory 方法加載指定文件夾配置文件。SPI 註解解析過程比較簡單,無需多說。下面咱們來看一下 loadDirectory 作了哪些事情。

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        // fileName = 文件夾路徑 + type 全限定名 
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls;
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
                // 根據文件名加載全部的同名文件
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    // 加載資源
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

loadDirectory 方法先經過 classLoader 獲取全部資源連接,而後再經過 loadResource 方法加載資源。咱們繼續跟下去,看一下 loadResource 方法的實現。

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
            try {
                String line;
                // 讀取文件中內容
                while ((line = reader.readLine()) != null) {
                    final int ci = line.indexOf('#');
                    if (ci >= 0) {
                        line = line.substring(0, ci);
                    }
                    line = line.trim();
                    if (line.length() > 0) {
                        try {
                            String name = null;
                            int i = line.indexOf('=');
                            if (i > 0) {
                                // 以等於號 = 爲界,截取鍵與值
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                // 加載類,並經過 loadClass 方法對類進行緩存
                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                            }
                        } catch (Throwable t) {
                            IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                            exceptions.put(line, e);
                        }
                    }
                }
            } finally {
                reader.close();
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }

loadClass()方法主要是利用反射原理,根據類的權限定名加載成類,並存入緩存中

相關文章
相關標籤/搜索