中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SPI在Dubbo中的使用方法

發布時間:2021-06-17 14:41:58 來源:億速云 閱讀:157 作者:chen 欄目:開發技術

這篇文章主要介紹“SPI在Dubbo中的使用方法”,在日常操作中,相信很多人在SPI在Dubbo中的使用方法問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”SPI在Dubbo中的使用方法”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

目錄
  • 一、概述

  • 二、JDK自帶SPI

    • 2.1、代碼示例

    • 2.2、簡單分析

  • 三、SPI與雙親委派

    • 3.1、SPI加載到何處

    • 3.2、SPI是否破壞了雙親委派

  • 四、Dubbo SPI

    • 4.1、基本概念

    • 4.2、代碼示例

    • 4.3、源碼分析

  • 五、總結

    一、概述

    SPI 全稱為 Service Provider Interface,是一種模塊間組件相互引用的機制。其方案通常是提供方將接口實現類的全名配置在classPath下的指定文件中,由調用方讀取并加載。這樣需要替換某個組件時,只需要引入新的JAR包并在其中包含新的實現類和配置文件即可,調用方的代碼無需任何調整。優秀的SPI框架能夠提供單接口多實現類時的優先級選擇,由用戶指定選擇哪個實現。

    得益于這些能力,SPI對模塊間的可插拔機制和動態擴展提供了非常好的支撐。

    本文將簡單介紹JDK自帶的SPI,分析SPI和雙親委派的關系,進而重點分析DUBBO的SPI機制;比較兩者有何不同,DUBBO的SPI帶來了哪些額外的能力。

    二、JDK自帶SPI

    提供者在classPath或者jar包的META-INF/services/目錄創建以服務接口命名的文件,調用者通過java.util.ServiceLoader加載文件內容中指定的實現類。

    2.1、代碼示例

    首先定義一個接口Search

    search示例接口

    package com.example.studydemo.spi;
    public interface Search {
        void search();
    }

    實現類FileSearchImpl實現該接口

    文件搜索實現類

    package com.example.studydemo.spi;
    public class FileSearchImpl implements Search {
        @Override
        public void search() {
            System.out.println("文件搜索");
        }
    }

    實現類DataBaseSearchImpl實現該接口

    數據庫搜索實現類

    package com.example.studydemo.spi;
    public class DataBaseSearchImpl implements Search {
        @Override
        public void search() {
            System.out.println("數據庫搜索");
        }
    }

    在項目的META-INF/services文件夾下,創建Search文件

    SPI在Dubbo中的使用方法

    文件內容為:

    com.example.studydemo.spi.DataBaseSearchImpl
    com.example.studydemo.spi.FileSearchImpl

    測試:

    import java.util.ServiceLoader;
    public class JavaSpiTest {
        public static void main(String[] args) {
            ServiceLoader<Search> searches = ServiceLoader.load(Search.class);
            searches.forEach(Search::search);
        }
    }

    結果為:

    SPI在Dubbo中的使用方法

    2.2、簡單分析

    ServiceLoader作為JDK提供的一個服務實現查找工具類,調用自身load方法加載Search接口的所有實現類,然后可以使用for循環遍歷實現類進行方法調用。

    有一個疑問:META-INF/services/目錄是硬編碼的嗎,其它路徑行不行?答案是不行。

    跟進到ServiceLoader類中,第一行代碼就是private static final String PREFIX = “META-INF/services/”,所以SPI配置文件只能放在classPath或者jar包的這個指定目錄下面。

    ServiceLoader的文件載入路徑

    public final class ServiceLoader<S>
        implements Iterable<S>
    {
        //硬編碼寫死了文件路徑
        private static final String PREFIX = "META-INF/services/";
      
        // The class or interface representing the service being loaded
        private final Class<S> service;
      
        // The class loader used to locate, load, and instantiate providers
        private final ClassLoader loader;

    JDK SPI的使用比較簡單,做到了基本的加載擴展組件的功能,但有以下幾點不足:

    • 需要遍歷所有的實現并實例化,想要找到某一個實現只能循環遍歷,一個一個匹配;

    • 配置文件中只是簡單的列出了所有的擴展實現,而沒有給他們命名,導致在程序中很難去準確的引用它們;

    • 擴展之間彼此存在依賴,做不到自動注入和裝配,不提供上下文內的IOC和AOP功能;

    • 擴展很難和其他的容器框架集成,比如擴展依賴了一個外部spring容器中的bean,原生的JDK SPI并不支持。

    三、SPI與雙親委派

    3.1、SPI加載到何處

    基于類加載的雙親委派原則,由JDK內部加載的class默認應該歸屬于bootstrap類加載器,那么SPI機制加載的class是否也屬于bootstrap呢 ?

    答案是否定的,原生SPI機制通過ServiceLoader.load方法由外部指定類加載器,或者默認取Thread.currentThread().getContextClassLoader()線程上下文的類加載器,從而避免了class被載入bootstrap加載器。

    3.2、SPI是否破壞了雙親委派

    雙親委派的本質涵義是在rt.jar包和外部class之間建立一道classLoader的鴻溝,即rt.jar內的class不應由外部classLoader加載,外部class不應由bootstrap加載。

    SPI僅是提供了一種在JDK代碼內部干預外部class文件加載的機制,并未強制指定加載到何處;外部的class還是由外部的classLoader加載,未跨越這道鴻溝,也就談不上破壞雙親委派。

    原生ServiceLoader的類加載器

    //指定類加載器
    public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader)
    //默認取前線程上下文的類加載器
    public static <S> ServiceLoader<S> load(Class<S> service)

    四、Dubbo SPI

    Dubbo借鑒了Java SPI的思想,與JDK的ServiceLoader相對應的,Dubbo設計了ExtensionLoader類,其提供的功能比JDK更為強大。

    4.1、基本概念

    首先介紹一些基本概念,讓大家有一個初步的認知。

    擴展點(Extension Point):是一個Java的接口。

    擴展(Extension):擴展點的實現類

    擴展實例(Extension Instance):擴展點實現類的實例。

    自適應擴展實例(Extension Adaptive Instance)

    自適應擴展實例其實就是一個擴展類的代理對象,它實現了擴展點接口。在調用擴展點的接口方法時,會根據實際的參數來決定要使用哪個擴展。

    比如一個Search的擴展點,有一個search方法。有兩個實現FileSearchImpl和DataBaseSearchImpl。Search的自適應實例在調用接口方法的時候,會根據search方法中的參數,來決定要調用哪個Search的實現。

    如果方法參數中有name=FileSearchImpl,那么就調用FileSearchImpl的search方法。如果name=DataBaseSearchImpl,就調用DataBaseSearchImpl的search方法。自適應擴展實例在Dubbo中的使用非常廣泛。

    在Dubbo中每一個擴展點都可以有自適應的實例,如果我們沒有使用@Adaptive人工指定,Dubbo會使用字節碼工具自動生成一個。

    SPI Annotation

    作用于擴展點的接口上,表明該接口是一個擴展點,可以被Dubbo的ExtentionLoader加載

    Adaptive

    @Adaptive注解可以使用在類或方法上。用在方法上表示這是一個自適應方法,Dubbo生成自適應實例時會在方法中植入動態代理的代碼。方法內部會根據方法的參數來決定使用哪個擴展。

    @Adaptive注解用在類上代表該實現類是一個自適應類,屬于人為指定的場景,Dubbo就不會為該SPI接口生成代理類,最典型的應用如AdaptiveCompiler、AdaptiveExtensionFactory等。

    @Adaptive注解的值為字符串數組,數組中的字符串是key值,代碼中要根據key值來獲取對應的Value值,進而加載相應的extension實例。比如new String[]{“key1”,”key2”},表示會先在URL中尋找key1的值,

    如果找到則使用此值加載extension,如果key1沒有,則尋找key2的值,如果key2也沒有,則使用SPI注解的默認值,如果SPI注解沒有默認值,則將接口名按照首字母大寫分成多個部分,

    然后以'.'分隔,例如org.apache.dubbo.xxx.YyyInvokerWrapper接口名會變成yyy.invoker.wrapper,然后以此名稱做為key到URL尋找,如果仍沒有找到則拋出IllegalStateException異常。

    ExtensionLoader

    類似于Java SPI的ServiceLoader,負責擴展的加載和生命周期維護。ExtensionLoader的作用包括:解析配置文件加載extension類、生成extension實例并實現IOC和AOP、創建自適應的extension等,下文會重點分析。

    擴展名

    和Java SPI不同,Dubbo中的擴展都有一個名稱,用于在應用中引用它們。比如
    registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
    dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol

    加載路徑

    Java SPI從/META-INF/services目錄加載擴展配置,Dubbo從以下路徑去加載擴展配置文件:
    META-INF/dubbo/internal
    META-INF/dubbo
    META-INF/services
    其中META-INF/dubbo對開發者發放,META-INF/dubbo/internal 這個路徑是用來加載Dubbo內部的拓展點的。

    4.2、代碼示例

    定義一個接口,標注上dubbo的SPI注解,賦予默認值,并提供兩個extension實現類

    package com.example.studydemo.spi;
    @SPI("dataBase")
    public interface Search {
        void search();
    }
    public class FileSearchImpl implements Search {
        @Override
        public void search() {
            System.out.println("文件搜索");
        }
    }
    public class DataBaseSearchImpl implements Search {
        @Override
        public void search() {
            System.out.println("數據庫搜索");
        }
    }

    在META-INF/dubbo 路徑下創建Search文件

     SPI在Dubbo中的使用方法

    文件內容如下:

    dataBase=com.example.studydemo.spi.DataBaseSearchImpl
    file=com.example.studydemo.spi.FileSearchImpl

    編寫測試類進行測試,內容如下:

    public class DubboSpiTest {
        public static void main(String[] args) {
            ExtensionLoader<Search> extensionLoader = ExtensionLoader.getExtensionLoader(Search.class);
            Search fileSearch = extensionLoader.getExtension("file");
            fileSearch.search();
            Search dataBaseSearch = extensionLoader.getExtension("dataBase");
            dataBaseSearch.search();
            System.out.println(extensionLoader.getDefaultExtensionName());
            Search defaultSearch = extensionLoader.getDefaultExtension();
            defaultSearch.search();
        }
    }

    結果為:

    SPI在Dubbo中的使用方法 SPI在Dubbo中的使用方法

    從代碼示例上來看,Dubbo SPI與Java SPI在這幾方面是類似的:

    • 接口及相應的實現

    • 配置文件

    • 加載類及加載具體實現

    4.3、源碼分析

    下面深入到源碼看看SPI在Dubbo中是怎樣工作的,以Protocol接口為例進行分析。

    //1、得到Protocol的擴展加載對象extensionLoader,由這個加載對象獲得對應的自適應擴展類
    Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
    //2、根據擴展名獲取對應的擴展類
    Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo");

    在獲取擴展實例前要先獲取Protocol接口的ExtensionLoader組件,通過ExtensionLoader來獲取相應的Protocol實例Dubbo實際是為每個SPI接口都創建了一個對應的ExtensionLoader。

    ExtensionLoader組件

    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        if(!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        if(!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }
        //EXTENSION_LOADERS為ConcurrentMap,存儲Class對應的ExtensionLoader
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

    EXTENSION_LOADERS是一個 ConcurrentMap,以接口Protocol為key,以ExtensionLoader對象為value;保存的是Protocol擴展的加載類,第一次加載的時候Protocol還沒有自己的接口加載類,需要實例化一個。

    再看new ExtensionLoader<T>(type) 這個操作,下面為ExtensionLoader的構造方法:

    rivate ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

    每一個ExtensionLoader都包含2個值:type和objectFactory,此例中type就是Protocol,objectFactory就是ExtensionFactory。

    對于ExtensionFactory接口來說,它的加載類中objectFactory值為null。

    對于其他的接口來說,objectFactory都是通過ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()來獲取;objectFactory的作用就是為dubbo的IOC提供依賴注入的對象,可以認為是進程內多個組件容器的一個上層引用,

    隨著這個方法的調用次數越來越多,EXTENSION_LOADERS 中存儲的 loader 也會越來越多。

    自適應擴展類與IOC

    得到ExtensionLoader組件之后,再看如何獲得自適應擴展實例。

    public T getAdaptiveExtension() {
        //cachedAdaptiveInstance為緩存的自適應對象,第一次調用時還沒有創建自適應類,所以instance為null
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if(createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            //創建自適應對象實例
                            instance = createAdaptiveExtension();
                            //將自適應對象放到緩存中
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            }
            else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }
      
        return (T) instance;
    }

    首先從cachedAdaptiveInstance緩存中獲取,第一次調用時還沒有相應的自適應擴展,需要創建自適應實例,創建后再將該實例放到cachedAdaptiveInstance緩存中。

    創建自適應實例參考createAdaptiveExtension方法,該方法包含兩部分內容:創建自適應擴展類并利用反射實例化、利用IOC機制為該實例注入屬性。

    private T createAdaptiveExtension() {
        try {
            //得到自適應擴展類并利用反射實例化,然后注入屬性值
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
        }
    }

    再來分析getAdaptiveExtensionClass方法,以Protocol接口為例,該方法會做以下事情:獲取所有實現Protocol接口的擴展類、如果有自適應擴展類直接返回、如果沒有則創建自適應擴展類。

    //該動態代理生成的入口
    private Class<?> getAdaptiveExtensionClass() {
        //1.獲取所有實現Protocol接口的擴展類
        getExtensionClasses();
        //2.如果有自適應擴展類,則返回
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        //3.如果沒有,則創建自適應擴展類
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

    getExtensionClasses方法會加載所有實現Protocol接口的擴展類,首先從緩存中獲取,緩存中沒有則調用loadExtensionClasses方法進行加載并設置到緩存中,如下圖所示:

    private Map<String, Class<?>> getExtensionClasses() {
        //從緩存中獲取
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    //從SPI配置文件中解析
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

    loadExtensionClasses方法如下:首先獲取SPI注解中的value值,作為默認擴展名稱,在Protocol接口中SPI注解的value為dubbo,因此DubboProtocol就是Protocol的默認實現擴展。其次加載三個配置路徑下的所有的Protocol接口的擴展實現。

    // 此方法已經getExtensionClasses方法同步過。
    private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if(defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if(value != null && (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<?>>();
        //分別從三個路徑加載
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }
      
      
    private static final String SERVICES_DIRECTORY = "META-INF/services/";
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

    在加載配置路徑下的實現中,其中有一個需要關注的點,如果其中某個實現類上有Adaptive注解,說明用戶指定了自適應擴展類,那么該實現類就會被賦給cachedAdaptiveClass,在getAdaptiveExtensionClass方法中會被直接返回。

    如果該變量為空,則需要通過字節碼工具來創建自適應擴展類。

    private Class<?> createAdaptiveExtensionClass() {
        //生成類代碼
        String code = createAdaptiveExtensionClassCode();
        //找到類加載器
        ClassLoader classLoader = findClassLoader();
        //獲取編譯器實現類,此處為AdaptiveCompiler,此類上有Adaptive注解
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        //將類代碼編譯為Class
        return compiler.compile(code, classLoader);
    }

    createAdaptiveExtensionClass方法生成的類代碼如下:

    package com.alibaba.dubbo.rpc;
      
    import com.alibaba.dubbo.common.extension.ExtensionLoader;
      
    public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
        public void destroy() {
            throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
        }
      
        public int getDefaultPort() {
            throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
        }
      
        public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
            if (arg1 == null) throw new IllegalArgumentException("url == null");
            com.alibaba.dubbo.common.URL url = arg1;
            String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
            if (extName == null)
                throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
            com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
            return extension.refer(arg0, arg1);
        }
      
        public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
            if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
            if (arg0.getUrl() == null)
                throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
            com.alibaba.dubbo.common.URL url = arg0.getUrl();
            String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
            if (extName == null)
                throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
            com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
            return extension.export(arg0);
        }
    } 

    由字節碼工具生成的類Protocol$Adpative在方法末尾調用了ExtensionLoader.getExtensionLoader(xxx).getExtension(extName)來滿足adaptive的自適應動態特性。

    傳入的extName就是從url中獲取的動態參數,用戶只需要在代表DUBBO全局上下文信息的URL中指定protocol參數的取值,adaptiveExtentionClass就可以去動態適配不同的擴展實例。

    再看屬性注入方法injectExtension,針對public的只有一個參數的set方法進行處理,利用反射進行方法調用來實現屬性注入,此方法是Dubbo SPI實現IOC功能的關鍵。

    private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;

    Dubbo IOC 是通過set方法注入依賴,Dubbo首先會通過反射獲取到實例的所有方法,然后再遍歷方法列表,檢測方法名是否具有set方法特征。若有則通過ObjectFactory獲取依賴對象。

    最后通過反射調用set方法將依賴設置到目標對象中。objectFactory在創建加載類ExtensionLoader的時候已經創建了,因為@Adaptive是打在類AdaptiveExtensionFactory上,所以此處就是AdaptiveExtensionFactory。

    AdaptiveExtensionFactory持有所有ExtensionFactory對象的集合,dubbo內部默認實現的對象工廠是SpiExtensionFactory和SpringExtensionFactory,他們經過TreeSet排好序,查找順序是優先先從SpiExtensionFactory獲取,如果返回空在從SpringExtensionFactory獲取。

    //有Adaptive注解說明該類是自適應類,不需要程序自己創建代理類
    @Adaptive
    public class AdaptiveExtensionFactory implements ExtensionFactory {
        //factories擁有所有ExtensionFactory接口的實現對象
        private final List<ExtensionFactory> factories;
          
        public AdaptiveExtensionFactory() {
            ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
            List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
            for (String name : loader.getSupportedExtensions()) {
                list.add(loader.getExtension(name));
            }
            factories = Collections.unmodifiableList(list);
        }
        //查找時會遍歷factories,順序優先從SpiExtensionFactory中獲取,再從SpringExtensionFactory中獲取,原因為初始化時getSupportedExtensions方法中使用TreeSet已經排序,見下圖
        public <T> T getExtension(Class<T> type, String name) {
            for (ExtensionFactory factory : factories) {
                T extension = factory.getExtension(type, name);
                if (extension != null) {
                    return extension;
                }
            }
            return null;
        }
    }
    public Set<String> getSupportedExtensions() {
        Map<String, Class<?>> clazzes = getExtensionClasses();
        return Collections.unmodifiableSet(new TreeSet<String>(clazzes.keySet()));
    }

    雖然有過度設計的嫌疑,但我們不得不佩服dubbo SPI設計的精巧。

    • 提供@Adaptive注解,既可以加在方法上通過參數動態適配到不同的擴展實例;又可以加在類上直接指定自適應擴展類。

    • 利用AdaptiveExtensionFactory統一了進程中的不同容器,將ExtensionLoader本身視為一個獨立的容器,依賴注入時將會分別從Spring容器和ExtensionLoader容器中查找。

    擴展實例和AOP

    getExtension方法比較簡單,重點在于createExtension方法,根據擴展名創建擴展實例。

    public T getExtension(String name) {
       if (name == null || name.length() == 0)
           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;
    }

    createExtension方法中的部分內容上文已經分析過了,getExtensionClasses方法獲取接口的所有實現類,然后通過name獲取對應的Class。緊接著通過clazz.newInstance()來實例化該實現類,調用injectExtension為實例注入屬性。

    private T createExtension(String name) {
        //getExtensionClasses方法之前已經分析過,獲取所有的擴展類,然后根據擴展名獲取對應的擴展類
        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, (T) clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //屬性注入
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && wrapperClasses.size() > 0) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    //包裝類的創建及屬性注入
                    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);
        }
    }

    在方法的最后有一段對于WrapperClass包裝類的處理邏輯,如果接口存在包裝類實現,那么就會返回包裝類實例。實現AOP的關鍵就是WrapperClass機制,判斷一個擴展類是否是WrapperClass的依據,是看其constructor函數中是否包含當前接口參數。

    如果有就認為是一個wrapperClass,最終創建的實例是一個經過多個wrapperClass層層包裝的結果;在每個wrapperClass中都可以編入面向切面的代碼,從而就簡單實現了AOP功能。

    Activate活性擴展

    對應ExtensionLoader的getActivateExtension方法,根據多個過濾條件從extension集合中智能篩選出您所需的那一部分。

    getActivateExtension方法

    public List<T> getActivateExtension(URL url, String[] names, String group);

    首先這個方法只會返回帶有Activate注解的擴展類,但并非帶有注解的擴展類都會被返回。

    names是明確指定所需要的那部分擴展類,非明確指定的擴展類需要滿足group過濾條件和Activate注解本身指定的key過濾條件,非明確指定的會按照Activate注解中指定的排序規則進行排序;

    getActivateExtension的返回結果是上述兩種擴展類的總和。

    Activate注解類

    */
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.METHOD})
    public @interface Activate {
        /**
         * Group過濾條件。
         */
        String[] group() default {};
      
        /**
         * Key過濾條件。包含{@link ExtensionLoader#getActivateExtension}的URL的參數Key中有,則返回擴展。
         */
        String[] value() default {};
      
        /**
         * 排序信息,可以不提供。
         */
        String[] before() default {};
      
        /**
         * 排序信息,可以不提供。
         */
        String[] after() default {};
      
        /**
         * 排序信息,可以不提供。
         */
        int order() default 0;
    }

    活性Extension最典型的應用是rpc invoke時獲取filter鏈條,各種filter有明確的執行優先級,同時也可以人為增添某些filter,filter還可以根據服務提供者和消費者進行分組過濾。

    以TokenFilter為例,其注解為@Activate(group = Constants.PROVIDER, value = Constants.TOKEN_KEY),表示該過濾器只在服務提供方才會被加載,同時會驗證注冊地址url中是否帶了token參數,如果有token表示服務端注冊時指明了要做token驗證,自然就需要加載該filter。

    反之則不用加載;此filter加載后的執行邏輯則是從url中獲取服務端注冊時預設的token,再從rpc請求的attachments中獲取消費方設置的remote token,比較兩者是否一致,若不一致拋出RPCExeption異常阻止消費方的正常調用。

    五、總結

    Dubbo 所有的接口幾乎都預留了擴展點,根據用戶參數來適配不同的實現。如果想增加新的接口實現,只需要按照SPI的規范增加配置文件,并指向新的實現即可。

    用戶配置的Dubbo屬性都會體現在URL全局上下文參數中,URL貫穿了整個Dubbo架構,是Dubbo各個layer組件間相互調用的紐帶。

    總結一下 Dubbo SPI 相對于 Java SPI 的優勢:

    • Dubbo的擴展機制設計默認值,每個擴展類都有自己的名稱,方便查找。

    • Dubbo的擴展機制支持IOC,AOP等高級功能。

    • Dubbo的擴展機制能和第三方IOC容器兼容,默認支持Spring Bean,也可擴展支持其他容器。

    • Dubbo的擴展類通過@Adaptive注解實現了動態代理功能,更強大的是它可以通過一個proxy映射多個不同的擴展類。

    • Dubbo的擴展類通過@Activate注解實現了不同擴展類的分組、過濾、排序功能,能夠更好的適配較復雜的業務場景。

    到此,關于“SPI在Dubbo中的使用方法”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    广元市| 邢台县| 榆树市| 紫金县| 光泽县| 宁津县| 同心县| 榆中县| 门头沟区| 潞城市| 大荔县| 稻城县| 台州市| 扎囊县| 林州市| 云龙县| 威宁| 锡林郭勒盟| 霍邱县| 陇川县| 曲松县| 疏附县| 玉溪市| 瓮安县| 临夏县| 凤冈县| 库尔勒市| 桑日县| 罗定市| 莱芜市| 五大连池市| 双峰县| 巴彦县| 西城区| 宾川县| 浮梁县| 哈巴河县| 浪卡子县| 固镇县| 台南县| 泰兴市|