Dubbo SPI機(jī)制詳解

本篇其實(shí)更應(yīng)該在Dubbo系列的最早去寫禁炒,只不過因?yàn)楫?dāng)初在讀源碼的時(shí)候沒有開始更新博客窜锯,所以漏了重要的一個章節(jié)饿凛,那就是Dubbo的SPI機(jī)制,所以這一章節(jié)主要是講解一下Dubbo的SPI機(jī)制實(shí)現(xiàn)移层。

我們都是知道一個合格的開源框架對于擴(kuò)展的支持都要是相當(dāng)彈性的,Dubbo 也不例外轻腺。Dubbo采用了簡單的擴(kuò)展方式劫哼,基于SPI機(jī)制。

什么是SPI样眠?
java spi的具體約定為:當(dāng)服務(wù)的提供者友瘤,提供了服務(wù)接口的一種實(shí)現(xiàn)之后,在jar包的META-INF/services/目錄里同時(shí)創(chuàng)建一個以服務(wù)接口命名的文件檐束。該文件里就是實(shí)現(xiàn)該服務(wù)接口的具體實(shí)現(xiàn)類辫秧。而當(dāng)外部程序裝配這個模塊的時(shí)候,就能通過該jar包META-INF/services/里的配置文件找到具體的實(shí)現(xiàn)類名被丧,并裝載實(shí)例化盟戏,完成模塊的注入。 基于這樣一個約定就能很好的找到服務(wù)接口的實(shí)現(xiàn)類甥桂,而不需要再代碼里制定柿究。jdk提供服務(wù)實(shí)現(xiàn)查找的一個工具類:java.util.ServiceLoader。 (網(wǎng)上摘錄的解釋)
Dubbo的SPI主要改進(jìn)了JDK標(biāo)準(zhǔn)的SPI實(shí)現(xiàn):

  • JDK標(biāo)準(zhǔn)的SPI會一次性實(shí)例化擴(kuò)展點(diǎn)所有實(shí)現(xiàn)黄选,如果有擴(kuò)展實(shí)現(xiàn)初始化很耗時(shí)蝇摸,但如果沒用上也加載,會很浪費(fèi)資源。

  • 如果擴(kuò)展點(diǎn)加載失敗探入,連擴(kuò)展點(diǎn)的名稱都拿不到了狡孔。比如:JDK標(biāo)準(zhǔn)的ScriptEngine,通過getName();獲取腳本類型的名稱蜂嗽,但如果RubyScriptEngine因?yàn)樗蕾嚨膉ruby.jar不存在苗膝,導(dǎo)致RubyScriptEngine類加載失敗,這個失敗原因被吃掉了植旧,和ruby對應(yīng)不起來辱揭,當(dāng)用戶執(zhí)行ruby腳本時(shí),會報(bào)不支持ruby病附,而不是真正失敗的原因问窃。

  • 增加了對擴(kuò)展點(diǎn)IoC和AOP的支持,一個擴(kuò)展點(diǎn)可以直接setter注入其它擴(kuò)展點(diǎn)完沪。

基本上講到這里大家對于SPI可能有個大致的認(rèn)識域庇,但是要真正理解Dubbo的SPI,還是要仔細(xì)看一下源碼才可以覆积。
在理解Dubbo的SPI之前听皿,要明確幾個核心概念:

  • 擴(kuò)展點(diǎn) Dubbo作用靈活的框架,并不會強(qiáng)制所有用戶都一定使用Dubbo提供的某些架構(gòu)宽档。例如注冊中心(Registry)尉姨,Dubbo提供了zk和redis,但是如果我們更傾向于其他的注冊中心的話吗冤,我們可以替換掉Dubbo提供的注冊中心又厉。針對這種可被替換的技術(shù)實(shí)現(xiàn)點(diǎn)我們稱之為擴(kuò)展點(diǎn),類似的擴(kuò)展點(diǎn)有很多椎瘟,例如Protocol覆致,F(xiàn)ilter,Loadbalance等等降传。

  • Wrapper Dubbo在加載某個接口的擴(kuò)展類時(shí)候篷朵,如果某個實(shí)現(xiàn)中有一個拷貝類構(gòu)造函數(shù)勾怒,那么該接口實(shí)現(xiàn)就是該接口的包裝類婆排,此時(shí)Dubbo會在真正的實(shí)現(xiàn)類上層包裝上蓋Wrapper。即這個時(shí)候從ExtensionLoader中返回的實(shí)際擴(kuò)展類是被Wrapper包裝的接口實(shí)現(xiàn)類笔链。

  • Adaptive 這個自適應(yīng)的擴(kuò)展點(diǎn)比較難理解段只,所以這里直接以一個例子來講解:在RegistryProtocol中有一個屬性為Cluster,其中Protocol和Cluster都是Dubbo提供的擴(kuò)展點(diǎn)鉴扫,所以這時(shí)候當(dāng)我們真正在操作中使用cluster的時(shí)候究竟使用的哪一個cluster的實(shí)現(xiàn)類呢赞枕?是FailbackCluster還是FailoverCluster?Dubbo在加載一個擴(kuò)展點(diǎn)的時(shí)候如果發(fā)現(xiàn)其成員變量也是一個擴(kuò)展點(diǎn)并且有相關(guān)的set方法,就會在這時(shí)候?qū)⒃摂U(kuò)展點(diǎn)設(shè)置為一個自適應(yīng)的擴(kuò)展點(diǎn)炕婶,自適應(yīng)擴(kuò)展點(diǎn)(Adaptive)會在真正使用的時(shí)候從URL中獲取相關(guān)參數(shù)姐赡,來調(diào)用真正的擴(kuò)展點(diǎn)實(shí)現(xiàn)類。具體的實(shí)現(xiàn)會在下面的源碼中詳細(xì)解釋柠掂。對于Adaptive的理解其實(shí)個人推薦的是Dubbo開發(fā)者指南项滑,指南中有對于Adaptive的明確介紹。

  • Activate 官網(wǎng)的叫法是自激活涯贞,其實(shí)這個更合適的叫法我認(rèn)為是條件激活枪狂,我們還記得上一篇中有提到Filter的內(nèi)容,其中Filter鏈的獲取就是通過@Activate注解來確定的宋渔,所以Activate的作用主要是:提供一種選擇性激活的條件州疾,可以是我們通過相關(guān)的配置來確定激活哪些功能。

ExtensionLoader

之所以把ExtensionLoader標(biāo)這么大是因?yàn)槠涮匾始穑鳛檎麄€SPI的核心严蓖,ExtensionLoader起著無可替代的作用,下面的整篇文章都在圍繞著這個類進(jìn)行講解氧急,足以看出他是多么重要了谈飒。

鑒于ExtensionLoade的用法比較多的都是如下用法,我就以下面的調(diào)用為例開始介紹ExtensionLoader (調(diào)用的鏈路比較長态蒂,大家要耐心點(diǎn)哈)

    //在Dubbo源碼中大面積使用這種寫法杭措,都是獲得某個接口的適配類,在真正執(zhí)行的時(shí)候才決定最終的作用類
    private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
    
    //因?yàn)槊恳粋€擴(kuò)展類加載器只能加載特定的SPI擴(kuò)展的實(shí)現(xiàn)钾恢,所以要獲得某個擴(kuò)展的實(shí)現(xiàn)的話首先要找到他對應(yīng)的擴(kuò)展類加載器(ExtensionLoader)
    //一個擴(kuò)展接口的所有實(shí)現(xiàn)都是被同一個擴(kuò)展類加載器來加載的
    @SuppressWarnings("unchecked")
    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!");
        }
        //獲得某個擴(kuò)展類加載器的時(shí)候該接口必須被@SPI修飾才可以
        if(!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type + 
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }
        // 先從靜態(tài)緩存中獲取對應(yīng)的ExtensionLoader實(shí)例手素,每個接口對應(yīng)一個ExtensionLoader并且把映射關(guān)系都存儲在緩存之中
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            // 為Extension類型創(chuàng)建ExtensionLoader實(shí)例,并放入靜態(tài)緩存
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
    
    public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if(createAdaptiveInstanceError == null) {
                //采用單例模式的雙重判斷瘩蚪,這種模式感覺使用的范圍都比較廣泛
                //cachedAdaptiveInstance作為一個Holder(只有簡單的get和set方法)泉懦,也是一個鎖對象
                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;
    }
    
    // 創(chuàng)建一個接口的適配類
    private T createAdaptiveExtension() {
        try {
            //獲取AdaptiveExtensionClass并完成注入
            //基本分兩步:1.獲取適配器類 2.在適配器里面注入其他的擴(kuò)展點(diǎn)
            return injectExtension((T) getAdaptiveExtensionClass().newInstance()); 
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
        }
    }
    
    //獲得適配類有兩種途徑,第一就是某個實(shí)現(xiàn)類上被@Adaptive注解疹瘦,第二就是沒有實(shí)現(xiàn)類被注解崩哩,因此Dubbo會自動生成一個某個接口的適配類
    private Class<?> getAdaptiveExtensionClass() {
         //如果能找到被@Adaptive注解實(shí)現(xiàn)類
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        //找不到的話就自行創(chuàng)建一個適配類
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
    
     //加載當(dāng)前擴(kuò)展所有實(shí)現(xiàn),看是否有實(shí)現(xiàn)類上被標(biāo)注為@Adaptive
    private Map<String, Class<?>> getExtensionClasses() {
        //多次判斷是為了防止同一個擴(kuò)展點(diǎn)被多次加載
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    //loadExtensionClasses會加載所有的配置文件言沐,將配置文件中對應(yīng)的的類加載到當(dāng)前的緩存中
                    //load完之后該classes已經(jīng)保留了所有的擴(kuò)展類映射關(guān)系
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }
    
    private Map<String, Class<?>> loadExtensionClasses() {
        //所有的擴(kuò)展點(diǎn)接口都必須被SPI注釋標(biāo)注
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if(defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if(value != null && (value = value.trim()).length() > 0) {
                //一個@SPI注解的值只能有一個
                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));
                }
                //cachedDefaultName表示該擴(kuò)展點(diǎn)對應(yīng)的默認(rèn)適配類的key
                //邏輯運(yùn)行到這里就意味著該擴(kuò)展點(diǎn)有定義的適配類邓嘹,不需要Dubbo框架自己生成適配類
                if(names.length == 1) cachedDefaultName = names[0];
            }
        }
        
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        //加載對應(yīng)目錄下的配置文件,三個目錄分別為:META-INF/services/险胰,META-INF/dubbo汹押,META-INF/dubbo/internal
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }
    
    //加載相關(guān)路徑下的類文件
    private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
        String fileName = dir + type.getName();
        try {
            Enumeration<java.net.URL> urls;
            //加載這些問價(jià)你的classloader要和加載當(dāng)前類的classloader一致,這個類似與Java默認(rèn)的類加載器和類的加載關(guān)系
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
               //該步驟就加載所有的classpath下面的同名文件(包含你的項(xiàng)目本地classpath和依賴jar包)
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                //一般情況下每個包內(nèi)只會對與每個擴(kuò)展點(diǎn)放置一個類信息描述文件
                while (urls.hasMoreElements()) {
                    java.net.URL url = urls.nextElement();
                    try {
                        BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
                        try {
                            String line = null;
                            while ((line = reader.readLine()) != null) {
                                //處理注釋內(nèi)容
                                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();//SPI擴(kuò)展文件中的key
                                            line = line.substring(i + 1).trim();//SPI擴(kuò)展文件中配置的value  ExtensionLoader是根據(jù)key和value同時(shí)加載的
                                        }
                                        if (line.length() > 0) {
                                            //加載擴(kuò)展類
                                            Class<?> clazz = Class.forName(line, true, classLoader);
                                            //如果配置的擴(kuò)展類實(shí)現(xiàn)不是目標(biāo)接口的實(shí)現(xiàn)類則直接跑錯
                                            if (! type.isAssignableFrom(clazz)) {
                                                throw new IllegalStateException("Error when load extension class(interface: " +
                                                        type + ", class line: " + clazz.getName() + "), class " 
                                                        + clazz.getName() + "is not subtype of interface.");
                                            }
                                            //如果配置的類是被@Adaptive注解的話
                                            if (clazz.isAnnotationPresent(Adaptive.class)) {
                                                if(cachedAdaptiveClass == null) {
                                                    //將緩存的AdaptiveClass設(shè)置成此類
                                                    cachedAdaptiveClass = clazz;
                                                    // 一個接口只能有一個適配類
                                                } else if (! cachedAdaptiveClass.equals(clazz)) {
                                                    throw new IllegalStateException("More than 1 adaptive class found: "
                                                            + cachedAdaptiveClass.getClass().getName()
                                                            + ", " + clazz.getClass().getName());
                                                }
                                            } else {
                                                try {
                                                    //判斷有沒有拷貝構(gòu)造函數(shù),如果有的話說明該類是實(shí)現(xiàn)的包裝類起便,進(jìn)行緩存棚贾。一個接口可能有多個對應(yīng)的包裝類實(shí)現(xiàn)
                                                    clazz.getConstructor(type);
                                                    Set<Class<?>> wrappers = cachedWrapperClasses;
                                                    if (wrappers == null) {
                                                        cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                                                        wrappers = cachedWrapperClasses;
                                                    }
                                                    wrappers.add(clazz);
                                                } catch (NoSuchMethodException e) {
                                                    clazz.getConstructor();
                                                    if (name == null || name.length() == 0) {
                                                        //兼容老邏輯窖维,這里可以暫時(shí)忽略
                                                        name = findAnnotationName(clazz);
                                                        if (name == null || name.length() == 0) {
                                                            if (clazz.getSimpleName().length() > type.getSimpleName().length()
                                                                    && clazz.getSimpleName().endsWith(type.getSimpleName())) {
                                                                name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
                                                            } else {
                                                                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
                                                            }
                                                        }
                                                    }
                                                    //將配置的key名字根據(jù)逗號來區(qū)分
                                                    String[] names = NAME_SEPARATOR.split(name);
                                                    if (names != null && names.length > 0) {
                                                        Activate activate = clazz.getAnnotation(Activate.class);
                                                        if (activate != null) {
                                                            //自激活的實(shí)現(xiàn)類只會將第一個name進(jìn)行存儲,原因到現(xiàn)在還有點(diǎn)不清楚
                                                            cachedActivates.put(names[0], activate);
                                                        }
                                                        for (String n : names) {
                                                            //假如說配置了name1,name2=com.alibaba.DemoInterface,這時(shí)候在cachedNames中只會存儲一個name1
                                                            if (! cachedNames.containsKey(clazz)) {
                                                                cachedNames.put(clazz, n);
                                                            }
                                                            Class<?> c = extensionClasses.get(n);
                                                            if (c == null) {
                                                                extensionClasses.put(n, clazz);
                                                            //防止同一個擴(kuò)展類實(shí)現(xiàn)被兩個key共同使用
                                                            } else if (c != clazz) {
                                                                throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    } catch (Throwable t) {
                                        IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
                                        exceptions.put(line, e);
                                    }
                                }
                            } // end of while read lines
                        } finally {
                            reader.close();
                        }
                    } catch (Throwable t) {
                        logger.error("Exception when load extension class(interface: " +
                                            type + ", class file: " + url + ") in " + url, t);
                    }
                } // end of while urls
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }
    
    //通過反射自動調(diào)用instance的set方法把自身的屬性注入進(jìn)去妙痹,解決的擴(kuò)展類依賴問題铸史,也就是說解決擴(kuò)展類依賴擴(kuò)展類的問題
    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())) { //如果該擴(kuò)展點(diǎn)實(shí)例有Set開頭的公共方法
                        Class<?> pt = method.getParameterTypes()[0];//得到set方法的參數(shù)類型
                        try {
                            //得到屬性名稱,比如setName方法就得到name屬性名稱
                            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);// 如果不為空怯伊,說明set方法的參數(shù)是擴(kuò)展點(diǎn)類型沛贪,那么進(jìn)行注入,意思也就是說擴(kuò)展點(diǎn)里面還有依賴其他擴(kuò)展點(diǎn)
                            }
                        } 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;
    }
    
    //通過反射自動調(diào)用instance的set方法把自身的屬性注入進(jìn)去震贵,解決的擴(kuò)展類依賴問題利赋,也就是說解決擴(kuò)展類依賴擴(kuò)展類的問題
    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())) { //如果該擴(kuò)展點(diǎn)實(shí)例有Set開頭的公共方法
                        Class<?> pt = method.getParameterTypes()[0];//得到set方法的參數(shù)類型
                        try {
                            //得到屬性名稱,比如setName方法就得到name屬性名稱
                            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);// 如果不為空猩系,說明set方法的參數(shù)是擴(kuò)展點(diǎn)類型媚送,那么進(jìn)行注入,意思也就是說擴(kuò)展點(diǎn)里面還有依賴其他擴(kuò)展點(diǎn)
                            }
                        } 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;
    }

基本上講到之類寇甸,一個適配類已經(jīng)可以被加載出來了塘偎,但是由于上面的邏輯比較深,下面給出簡單文字邏輯:
1.為了獲得一個擴(kuò)展點(diǎn)的適配類拿霉,首先會看緩存中有沒有已經(jīng)加載過的適配類吟秩,如果有的話就直接返回,沒有的話就進(jìn)入第2步绽淘。
2.加載所有的配置文件涵防,將所有的配置類都load進(jìn)內(nèi)存并且在ExtensionLoader內(nèi)部做好緩存,如果配置的文件中有適配類就緩存起來沪铭,如果沒有適配類就自行通過代碼自行創(chuàng)建適配類并且緩存起來(代碼之后給出樣例)壮池。
3.在加載配置文件的時(shí)候,會依次將包裝類杀怠,自激活的類都進(jìn)行緩存椰憋。
4.將獲取完適配類時(shí)候,如果適配類的set方法對應(yīng)的屬性也是擴(kuò)展點(diǎn)話赔退,會依次注入對應(yīng)的屬性的適配類(循環(huán)進(jìn)行)橙依。
講到這里的話適配類這段代碼邏輯已經(jīng)大致都解釋過了,下面看一下Dubbo自己生成的適配類代碼是怎樣的(以Protocol為例):

    import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative implements Protocol {
  public Invoker refer(Class arg0, URL arg1) throws Class {
    if (arg1 == null) throw new IllegalArgumentException("url == null");

    URL url = arg1;
    String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );

    if(extName == null) throw new IllegalStateException("Fail to get extension(Protocol) name from url(" + url.toString() + ") use keys([protocol])");
    
    Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
    
    return extension.refer(arg0, arg1);
  }
  
  public Exporter export(Invoker arg0) throws Invoker {
    if (arg0 == null) throw new IllegalArgumentException("Invoker argument == null");
    
    if (arg0.getUrl() == null) throw new IllegalArgumentException("Invoker argument getUrl() == null");URL url = arg0.getUrl();
    //這里會根據(jù)url中的信息獲取具體的實(shí)現(xiàn)類名
    String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
    
    if(extName == null) throw new IllegalStateException("Fail to get extension(Protocol) name from url(" + url.toString() + ") use keys([protocol])");
    //根據(jù)上面的實(shí)現(xiàn)類名硕旗,會在運(yùn)行時(shí)窗骑,通過Dubbo的擴(kuò)展機(jī)制加載具體實(shí)現(xiàn)類
    Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
    
    return extension.export(arg0);
  }
  
  public void destroy() {
    throw new UnsupportedOperationException("method public abstract void Protocol.destroy() of interface Protocol is not adaptive method!");
  }
  
  public int getDefaultPort() {
    throw new UnsupportedOperationException("method public abstract int Protocol.getDefaultPort() of interface Protocol is not adaptive method!");
  }
}

本質(zhì)上的做法就是通過方法的參數(shù)獲得URL信息,從URL中獲得對應(yīng)的value對應(yīng)值卵渴,然后從ExtensionLoader的緩存中找到value對應(yīng)的具體實(shí)現(xiàn)類慧域,然后用該實(shí)現(xiàn)類進(jìn)行工作±硕粒可以看到上面的核心就是getExtension方法了昔榴,下面來看一下該方法的實(shí)現(xiàn):

    public T getExtension(String name) {
        if (name == null || name.length() == 0)
            throw new IllegalArgumentException("Extension name == null");
        //DefaultExtension就是自適應(yīng)的擴(kuò)展類
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        //先從緩存中去取
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            //如果緩存中沒有的話在創(chuàng)建一個然后放進(jìn)去,但是此時(shí)并沒有實(shí)際內(nèi)容碘橘,只有一個空的容器Holder
            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) {
                    //根據(jù)名字創(chuàng)建特定的擴(kuò)展
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }
    
    private T createExtension(String name) {
        //獲取name類型對應(yīng)的擴(kuò)展類型互订,從cachedClasses根據(jù)key獲取對應(yīng)的class,cachedClasses已經(jīng)在load操作的時(shí)候初始化過了痘拆。
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            //獲得擴(kuò)展類型對應(yīng)的實(shí)例
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                //將實(shí)例放進(jìn)緩存中
                EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //injectExtension方法的作用就是通過set方法注入其他的屬性擴(kuò)展點(diǎn)仰禽,上面已經(jīng)講過
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && wrapperClasses.size() > 0) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    //循環(huán)遍歷所有wrapper實(shí)現(xiàn),實(shí)例化wrapper并進(jìn)行擴(kuò)展點(diǎn)注入
                    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);
        }
    }

getExtension的步驟就是根據(jù)之前l(fā)oad操作生成的class緩存纺蛆,找到對應(yīng)的Class信息吐葵,然后根據(jù)Class生成具體的實(shí)例,生成之后通過反射注入其他的屬性擴(kuò)展點(diǎn)桥氏,將包裝類進(jìn)行層層包裝温峭,最終返回包裝過的類。(包裝邏輯可以參考Protocol的包裝實(shí)例)


理解ExtensionLoader是理解Dubbo的SPI的基礎(chǔ)字支,也是理解Dubbo擴(kuò)展機(jī)制的基礎(chǔ)凤藏,所以如果各位有耐心的話,還是請自己閱讀一些上面提到的源碼部分(基本上都有文字注釋)堕伪。

最后揖庄,習(xí)慣性的撒花~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市欠雌,隨后出現(xiàn)的幾起案子蹄梢,更是在濱河造成了極大的恐慌,老刑警劉巖富俄,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件检号,死亡現(xiàn)場離奇詭異,居然都是意外死亡蛙酪,警方通過查閱死者的電腦和手機(jī)齐苛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桂塞,“玉大人凹蜂,你說我怎么就攤上這事「笪#” “怎么了玛痊?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長狂打。 經(jīng)常有香客問我擂煞,道長,這世上最難降的妖魔是什么趴乡? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任对省,我火速辦了婚禮蝗拿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蒿涎。我一直安慰自己哀托,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布劳秋。 她就那樣靜靜地躺著仓手,像睡著了一般。 火紅的嫁衣襯著肌膚如雪玻淑。 梳的紋絲不亂的頭發(fā)上嗽冒,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天,我揣著相機(jī)與錄音补履,去河邊找鬼添坊。 笑死,一個胖子當(dāng)著我的面吹牛干像,可吹牛的內(nèi)容都是我干的帅腌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼麻汰,長吁一口氣:“原來是場噩夢啊……” “哼速客!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起五鲫,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤溺职,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后位喂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浪耘,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年塑崖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了七冲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡规婆,死狀恐怖澜躺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抒蚜,我是刑警寧澤掘鄙,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站嗡髓,受9級特大地震影響操漠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜饿这,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一浊伙、第九天 我趴在偏房一處隱蔽的房頂上張望撞秋。 院中可真熱鬧,春花似錦吧黄、人聲如沸部服。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至奉芦,卻和暖如春赵抢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背声功。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工烦却, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人先巴。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓其爵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親伸蚯。 傳聞我的和親對象是個殘疾皇子摩渺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 前面我們了解過了Java的SPI擴(kuò)展機(jī)制,對于Java擴(kuò)展機(jī)制的原理以及優(yōu)缺點(diǎn)也有了大概的了解剂邮,這里繼續(xù)深入一下D...
    加大裝益達(dá)閱讀 5,067評論 2 20
  • 以下內(nèi)容均來自 梁飛 的個人博客 http://javatar.iteye.com/blog/1056664[ht...
    高廣超閱讀 5,127評論 1 53
  • 這幾天摇幻,朋友圈已被『林丹出軌事件』霸屏,在譴責(zé)那位在老婆懷孕出軌的渣男之余挥萌,也祝福謝杏芳绰姻,希望她以后可以為自己活的...
    悠然之春閱讀 344評論 2 1
  • 2018年6月1日 今天將是女兒人生中度過的最后一個“六一”兒童節(jié),你知道這意味著什么嗎引瀑?孩子狂芋,媽媽告訴你,這意味...
    duhongjuan閱讀 747評論 0 0
  • 上午和母親去云龍公園玩憨栽,人不如想象中的多帜矾,景色比想象中的好。 有個拉二胡的地方徒像,風(fēng)景也很不錯黍特,我拍的照片都比較滿意...
    花農(nóng)不是花濃閱讀 246評論 1 1