Dubbo SPI機(jī)制分析【一】


title: Dubbo SPI機(jī)制分析
tags: Dubbo,SPI,源碼
grammar_cjkRuby: true


Dubbo 的擴(kuò)展點(diǎn)加載從 JDK 標(biāo)準(zhǔn)的 SPI (Service Provider Interface) 擴(kuò)展點(diǎn)發(fā)現(xiàn)機(jī)制加強(qiáng)而來(lái)晚碾。 Dubbo 改進(jìn)了 JDK 標(biāo)準(zhǔn)的 SPI 的以下問(wèn)題:

  • JDK 標(biāo)準(zhǔn)的 SPI 會(huì)一次性實(shí)例化擴(kuò)展點(diǎn)所有實(shí)現(xiàn),如果有擴(kuò)展實(shí)現(xiàn)初始化很耗時(shí),但如果沒(méi)用上也加載假瞬,會(huì)很浪費(fèi)資源。
  • 如果擴(kuò)展點(diǎn)加載失敗构回,連擴(kuò)展點(diǎn)的名稱都拿不到了嘉熊。比如:JDK 標(biāo)準(zhǔn)的 ScriptEngine遥赚,通過(guò) getName() 獲取腳本類型的名稱,但如果 RubyScriptEngine 因?yàn)樗蕾嚨?jruby.jar 不存在阐肤,導(dǎo)致 RubyScriptEngine 類加載失敗凫佛,這個(gè)失敗原因被吃掉了,和 ruby 對(duì)應(yīng)不起來(lái)孕惜,當(dāng)用戶執(zhí)行 ruby 腳本時(shí)愧薛,會(huì)報(bào)不支持 ruby,而不是真正失敗的原因衫画。
  • 增加了對(duì)擴(kuò)展點(diǎn) IoC 和 AOP 的支持毫炉,一個(gè)擴(kuò)展點(diǎn)可以直接 setter 注入其它擴(kuò)展點(diǎn)。

源碼分析

dubbo的Extension基本架構(gòu)如圖所示:


1539264553280.png

這里最重要的類就是ExtensionLoader.

獲取動(dòng)態(tài)自適應(yīng)拓展實(shí)現(xiàn)類

首先分析

ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()

getAdaptiveExtension()

首先分析此方法:

    public T getAdaptiveExtension() {
        //1削罩、先從自適應(yīng)實(shí)例緩存中獲取實(shí)例
        Object instance = cachedAdaptiveInstance.get();
        //2瞄勾、雙重鎖檢查,如果實(shí)例不存在鲸郊,則生成實(shí)例
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                //cachedAdaptiveInstance存放Adaptive修飾類的實(shí)例
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            //3丰榴、生成實(shí)例
                            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;
    }
  • 首先從自適應(yīng)實(shí)例緩存cachedAdaptiveInstance中獲取@Adaptive修飾的類
  • 如果實(shí)例為空,則進(jìn)行雙重鎖檢查模式創(chuàng)建一個(gè)自適應(yīng)適配器拓展點(diǎn)

createAdaptiveExtension

    private T createAdaptiveExtension() {
        try {
            //1秆撮、先獲取自適應(yīng)拓展點(diǎn)實(shí)現(xiàn)類四濒,然后實(shí)例化
            //2、注入
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create ad" +
                    "aptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

  • 首先通過(guò) getAdaptiveExtensionClass() 獲取對(duì)應(yīng)的class
  • 實(shí)例化
  • 注入

getAdaptiveExtensionClass

    private Class<?> getAdaptiveExtensionClass() {
        //加載當(dāng)前拓展點(diǎn)的所有實(shí)現(xiàn)职辨,如果有被@Adaptive修飾的實(shí)現(xiàn)類盗蟆,則緩存在cachedAdaptiveClass
        getExtensionClasses();
        //判斷是否有被@Adaptive修飾的實(shí)現(xiàn)類,有的話則返回
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        //沒(méi)有@Adaptive修飾的實(shí)現(xiàn)類舒裤,創(chuàng)建動(dòng)態(tài)自適應(yīng)拓展點(diǎn)實(shí)現(xiàn)
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
  • 通過(guò)getExtensionClasses加載當(dāng)前拓展點(diǎn)的所有實(shí)現(xiàn)
  • 如果有 @Adaptive 修飾的實(shí)現(xiàn)類喳资,則緩存在cachedAdaptiveClass,并返回
  • 如果沒(méi)有 @Adaptive 修飾的實(shí)現(xiàn)類腾供,則通過(guò)createAdaptiveExtensionClass創(chuàng)建動(dòng)態(tài)自適應(yīng)拓展點(diǎn)實(shí)現(xiàn)

getExtensionClasses

首先分析getExtensionClasses

    private Map<String, Class<?>> getExtensionClasses() {
        //判斷拓展點(diǎn)實(shí)現(xiàn)類緩存cachedClasses是否為空仆邓,如果為空,通過(guò)雙重所檢查模式進(jìn)行拓展點(diǎn)實(shí)現(xiàn)類加載
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    //加載拓展點(diǎn)實(shí)現(xiàn)類
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

判斷拓展點(diǎn)實(shí)現(xiàn)類緩存cachedClasses是否為空伴鳖,如果為空节值,通過(guò)雙重鎖檢查模式進(jìn)行拓展點(diǎn)實(shí)現(xiàn)類加載loadExtensionClasses

loadExtensionClasses

    private Map<String, Class<?>> loadExtensionClasses() {
        //判斷當(dāng)前類型是否是可拓展點(diǎn)且獲取默認(rèn)拓展實(shí)現(xiàn)類
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if ((value = value.trim()).length() > 0) {
                //每個(gè)拓展接口只能有一個(gè)拓展實(shí)現(xiàn)默認(rèn)名,如Protocol的默認(rèn)實(shí)現(xiàn)是“dubbo”
                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));
                }
                //設(shè)置默認(rèn)名榜聂,如Protocol的為dubbo
                if (names.length == 1) cachedDefaultName = names[0];
            }
        }

        //根據(jù)傳入類型搞疗,對(duì)【META-INF/dubbo/internal/】 【META-INF/dubbo/】【 META-INF/services/】
        //進(jìn)行拓展點(diǎn)實(shí)現(xiàn)類的加載
        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() 做了這么幾件事:

  • 判斷當(dāng)前類型是否是拓展點(diǎn)接口,即傳入類型是否由@SPI修飾
  • 判斷SPI的name的個(gè)數(shù)须肆,如果大于1匿乃,則拋錯(cuò)桩皿;否則獲取name的值,然后將它設(shè)置為默認(rèn)值 cachedDefaultName
  • 對(duì) META-INF/dubbo/internal/ META-INF/dubbo/ META-INF/services/ 進(jìn)行拓展點(diǎn)實(shí)現(xiàn)類的加載

loadDirectory

    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        //拼接文件名幢炸,如META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls;
            ClassLoader classLoader = findClassLoader();
            //加載classpath下所有對(duì)應(yīng)type的文件泄隔,如META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol,然后進(jìn)行合并加載
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                //遍歷所有對(duì)應(yīng)type的文件阳懂,如org.apache.dubbo.rpc.Protocol梅尤,然后加載class
                while (urls.hasMoreElements()) {
                    //獲取絕對(duì)路徑
                    java.net.URL resourceURL = urls.nextElement();
                    //獲取jar下對(duì)應(yīng)文件的class
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }
  • 將對(duì)應(yīng)的文檔以及類型全路徑進(jìn)行拼接,如META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol
  • 遍歷classpath對(duì)應(yīng)路徑下的文件岩调,然后掃描加載
    進(jìn)入loadResource,讀取文件內(nèi)容

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;
                //輪流讀取行數(shù)據(jù)赡盘,獲取拓展接口實(shí)現(xiàn)類
                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) {
                                //循環(huán)加載拓展接口實(shí)現(xiàn)類
                                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);
        }
    }

這個(gè)方法做的事情很簡(jiǎn)單:

  • 按行讀取文件里面的內(nèi)容
  • 將每一行的內(nèi)容截取到“#”為止号枕,然后以“=”為分隔符,取等號(hào)前面的內(nèi)容為name陨享,等號(hào)后面的內(nèi)容為拓展點(diǎn)實(shí)現(xiàn)類的全路徑
  • 根據(jù)name和拓展點(diǎn)實(shí)現(xiàn)類全路徑葱淳,加載class

loadClass

    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        //判斷是否實(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.");
        }
        //如果是自定義的適配拓展實(shí)現(xiàn)類,則設(shè)置cachedAdaptiveClass抛姑,并返回
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            if (cachedAdaptiveClass == null) {
                cachedAdaptiveClass = clazz;
            } else if (!cachedAdaptiveClass.equals(clazz)) {
                throw new IllegalStateException("More than 1 adaptive class found: "
                        + cachedAdaptiveClass.getClass().getName()
                        + ", " + clazz.getClass().getName());
            }
        //如果是包裝類的話赞厕,走包裝類的邏輯,即加載存儲(chǔ)在cachedWrapperClasses:ProtocolFilterWrapper定硝,ProtocolListenerWrapper
        } else if (isWrapperClass(clazz)) {
            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
            }
            wrappers.add(clazz);
        } else {
            //如果沒(méi)有指定自定義的適配拓展實(shí)現(xiàn)類皿桑,且沒(méi)有包裝類,說(shuō)明是普通的拓展點(diǎn)實(shí)現(xiàn)類
            clazz.getConstructor();
            if (name == null || name.length() == 0) {
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }
            //分割name蔬啡,獲取name數(shù)組
            String[] names = NAME_SEPARATOR.split(name);
            if (names != null && names.length > 0) {
                //判斷是否被@Active修飾的拓展點(diǎn)實(shí)現(xiàn)類诲侮,如果是,則使用cachedActivates緩存
                Activate activate = clazz.getAnnotation(Activate.class);
                if (activate != null) {
                    cachedActivates.put(names[0], activate);
                } else {
                    // support com.alibaba.dubbo.common.extension.Activate
                    com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
                    if (oldActivate != null) {
                        cachedActivates.put(names[0], oldActivate);
                    }
                }
                //同一個(gè)實(shí)現(xiàn)類對(duì)應(yīng)多個(gè)name箱蟆,以name=>class形式存放于extensionClasses沟绪,adaptive修飾的不會(huì)存放,包裝類不會(huì)存放
                for (String n : names) {
                    if (!cachedNames.containsKey(clazz)) {
                        cachedNames.put(clazz, n);
                    }
                    Class<?> c = extensionClasses.get(n);
                    if (c == null) {
                        extensionClasses.put(n, clazz);
                    } else if (c != clazz) {
                        throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                    }
                }
            }
        }
    }

這個(gè)方法做了這么幾件事:

  • 攔截沒(méi)有實(shí)現(xiàn)指定類型的拓展點(diǎn)實(shí)現(xiàn)類

  • 如果傳入class是由@Adaptive修飾空猜,那么使用cachedAdaptiveClass緩存绽慈,只能緩存一個(gè)

  • 如果是包裝類,符合以下條件clazz.getConstructor(type),使用SetcachedWrapperClasses)存儲(chǔ)

    1539323479853.png

  • 除了兩種類型之外辈毯,剩下的就是普通的拓展點(diǎn)實(shí)現(xiàn)類坝疼,判斷是否由@Active的自動(dòng)激活的類型,如果是漓摩,使用Map緩存cachedActivates以name->Active的形式存儲(chǔ)裙士;然后分割name得到對(duì)應(yīng)的name數(shù)組,遍歷數(shù)組,使用cachedNames緩存以class->name的形式存儲(chǔ)管毙,同時(shí)腿椎,以name->class的形式存儲(chǔ)在緩存cachedClasses

至此桌硫,getExtensionClasses的邏輯走完,梳理下它的邏輯:
1啃炸、判斷拓展點(diǎn)實(shí)現(xiàn)類的緩存cachedClasses是否為空铆隘,如果為空,進(jìn)行加載操作
2南用、根據(jù)傳入類型膀钠,獲取SPI注解的值,然后設(shè)置默認(rèn)名cachedDefaultNam
3裹虫、在META-INF/dubbo/internal/ META-INF/dubbo/ META-INF/services/這三個(gè)目錄下進(jìn)行拓展點(diǎn)加載
4肿嘲、遍歷文件(文件名為傳入類型type的全路徑名稱)內(nèi)容,然后根據(jù)name->className加載class

  • 如果是@Adaptive修飾筑公,那么將當(dāng)前class緩存在cachedAdaptiveClass,該值是單例
  • 如果是包裝類雳窟,如ProtocolFilterWrapperProtocolListenerWrapper匣屡,則將class緩存在set集合cachedWrapperClasses
  • 如果是普通的拓展點(diǎn)實(shí)現(xiàn)類
    • 如果當(dāng)前類是@Active修飾的封救,那么使用緩存cachedActivatesname->Active的形式進(jìn)行緩存;
    • name","的形式進(jìn)行分割成數(shù)組,然后遍歷;用緩存cachedNamesclass->name的形式存儲(chǔ)當(dāng)前拓展點(diǎn)實(shí)現(xiàn)類的多個(gè)名稱捣作,同時(shí)誉结,以name->class的形式將拓展點(diǎn)實(shí)現(xiàn)類緩存在cachedClasses

到這里,已經(jīng)完成了相關(guān)拓展點(diǎn)實(shí)現(xiàn)類券躁、@Adaptive修飾的拓展點(diǎn)實(shí)現(xiàn)類惩坑、@Active修飾的拓展點(diǎn)實(shí)現(xiàn)類、包裝Wrapper拓展點(diǎn)實(shí)現(xiàn)類的加載嘱朽,回到getAdaptiveExtensionClass,當(dāng)前緩存cachedAdaptiveClass如果不為空旭贬,則說(shuō)明有@Adaptive修飾的拓展點(diǎn)實(shí)現(xiàn)類,直接返回搪泳,如果沒(méi)有稀轨,則開始創(chuàng)建動(dòng)態(tài)自適應(yīng)拓展點(diǎn)createAdaptiveExtensionClass.

createAdaptiveExtensionClass

    private Class<?> createAdaptiveExtensionClass() {
        String code = createAdaptiveExtensionClassCode();
        ClassLoader classLoader = findClassLoader();
        //動(dòng)態(tài)編譯Extension,名字為typeName+"$Adaptive",如Protocol$Adaptive
        //Protocol$Adaptive的主要功能
        //1. 從url或擴(kuò)展接口獲取擴(kuò)展接口實(shí)現(xiàn)類的名稱岸军;
        //2.根據(jù)名稱奋刽,獲取實(shí)現(xiàn)類ExtensionLoader.getExtensionLoader(擴(kuò)展接口類).getExtension(擴(kuò)展接口實(shí)現(xiàn)類名稱),然后調(diào)用實(shí)現(xiàn)類的方法艰赞。
        //需要明白一點(diǎn)dubbo的內(nèi)部傳參基本上都是基于Url來(lái)實(shí)現(xiàn)的佣谐,也就是說(shuō)Dubbo是基于URL驅(qū)動(dòng)的技術(shù)
        //所以,適配器類的目的是在運(yùn)行期獲取擴(kuò)展的真正實(shí)現(xiàn)來(lái)調(diào)用方妖,解耦接口和實(shí)現(xiàn)狭魂,這樣的話要不我們自己實(shí)現(xiàn)適配器類,要不dubbo幫我們生成,而這些都是通過(guò)Adpative來(lái)實(shí)現(xiàn)雌澄。
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

上面這個(gè)方法斋泄,通過(guò)動(dòng)態(tài)代理直接生成class,名為typeName+$Adaptive镐牺,debug得到Protocol$Adaptive,源碼內(nèi)容為

package com.wl.dubbo;

/**
 * @Author: liumenglong
 * @Date: 2018/9/29 22:48
 * @Description:
 */

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adaptive 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;
        //根據(jù)URL獲取到對(duì)應(yīng)的拓展名炫掐,如果沒(méi)有指定,則默認(rèn)取“dubbo”
        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])");
        //根據(jù)拓展名獲取對(duì)應(yīng)的拓展點(diǎn)實(shí)現(xiàn)類
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        //調(diào)用實(shí)際拓展點(diǎn)的refer方法睬涧,如DubboProtocol
        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();
        //根據(jù)URL獲取到對(duì)應(yīng)的拓展名募胃,如果沒(méi)有指定,則默認(rèn)取“dubbo”
        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])");
        //根據(jù)拓展名獲取對(duì)應(yīng)的拓展點(diǎn)實(shí)現(xiàn)類
        com.alibaba.dubbo.rpc.Protocol extension = 
                (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.
                        getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        //調(diào)用實(shí)際拓展點(diǎn)的export方法畦浓,如DubboProtocol
        return extension.export(arg0);
    }
}

何為動(dòng)態(tài)自適應(yīng)拓展點(diǎn)痹束,就是通過(guò)傳入的Url對(duì)象,獲取到相應(yīng)的拓展點(diǎn)名稱宅粥,根據(jù)拓展名獲取到具體的拓展點(diǎn)實(shí)現(xiàn)類参袱,從而調(diào)用該實(shí)現(xiàn)類的方法,這個(gè)方法很好的體現(xiàn)了對(duì)修改關(guān)閉對(duì)拓展開放的原則秽梅,用戶如果需要實(shí)現(xiàn)新的拓展點(diǎn)實(shí)現(xiàn)類,只需要按規(guī)則配置好相應(yīng)的類和文件即可剿牺,無(wú)需修改核心代碼企垦。

再回到createAdaptiveExtension,

 return injectExtension((T) getAdaptiveExtensionClass().newInstance());

上面的getAdaptiveExtensionClass已經(jīng)完成相應(yīng)class的加載,且通過(guò)newInstance完成了實(shí)例化晒来,接下來(lái)進(jìn)行注入injectExtension

injectExtension

private T injectExtension(T instance) {
    try {
        //如果當(dāng)前objectFactory不為空钞诡,說(shuō)明傳入type不是ExtensionFactory類型(具體原因見當(dāng)前類的構(gòu)造方法),則可以注入
        if (objectFactory != null) {
            for (Method method : instance.getClass().getMethods()) {
                //判斷方法是否以set為前綴湃崩,且方法參數(shù)個(gè)數(shù)是1個(gè)荧降,且方法是public的,如果是攒读,進(jìn)入注入邏輯
                if (method.getName().startsWith("set")
                        && method.getParameterTypes().length == 1
                        && Modifier.isPublic(method.getModifiers())) {
                    //獲取該方法的class類型
                    Class<?> pt = method.getParameterTypes()[0];
                    try {
                        //獲取屬性名稱
                        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                        // 根據(jù)類型和屬性名稱朵诫,獲取實(shí)例
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            //進(jìn)行注入
                            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;
}
  • 判斷objectFactory,如果objectFactory不為空薄扁,說(shuō)明說(shuō)明傳入type不是ExtensionFactory類型剪返,滿足注入的第一個(gè)條件
  • 遍歷當(dāng)前對(duì)象的方法,如果方法滿足以下條件邓梅,進(jìn)行注入
    • 前綴為"set"
    • public修飾的
    • 參數(shù)個(gè)數(shù)是一個(gè)
  • 獲取屬性的類型class以及注入的屬性的名稱脱盲,通過(guò)objectFactory進(jìn)行實(shí)例的獲取
  • 調(diào)用method.invoke(instance, object);進(jìn)行注入

這里首先分析objectFactory,因此需回到ExtensionLoader.getExtensionLoader,本來(lái)應(yīng)該先分析這個(gè)方法,但由于getExtensionLoader不影響前面加載拓展點(diǎn)實(shí)現(xiàn)類的邏輯日缨,故先不講解钱反,且前面加載拓展點(diǎn)實(shí)現(xiàn)類的邏輯是理解ExtensionLoader.getExtensionLoader的基礎(chǔ),因此,直到注入這里面哥,再對(duì)ExtensionLoader.getExtensionLoader進(jìn)行分析.

getExtensionLoader

  //工廠方法哎壳,根據(jù)傳入類型獲取對(duì)應(yīng)ExtensionLoader
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        //傳入的類型必須是接口且不為空,且必須是SPI修飾的可拓展點(diǎn)
        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!");
        }

        //ExtensionLoader會(huì)存放在緩存之中幢竹,EXTENSION_LOADERS:type=>ExtensionLoader
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            //將ExtensionLoader放到緩存EXTENSION_LOADERS之中
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

這里主要是對(duì)非SPI修飾的類進(jìn)行攔截耳峦,然后根據(jù)傳入類型typeEXTENSION_LOADERS緩存中獲取實(shí)例,如果為空焕毫,則進(jìn)行實(shí)例創(chuàng)建蹲坷,然后添加到緩存中。
對(duì)實(shí)例創(chuàng)建的過(guò)程new ExtensionLoader<T>(type)進(jìn)行解析:
進(jìn)入該構(gòu)造函數(shù):

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

由于當(dāng)前typeExtensionFactory,故調(diào)用ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension(),由上面可得邑飒,當(dāng)調(diào)用ExtensionLoader.getAdaptiveExtension的時(shí)候,如果該拓展點(diǎn)的實(shí)現(xiàn)類有被@Adaptive修飾的,則返回該實(shí)例循签,而查看ExtensionFactory接口的實(shí)現(xiàn)類可得,有符合該條件的實(shí)現(xiàn)類,如圖

1539342675108.png

因此疙咸,objectFactory不為空县匠,且是AdaptiveExtensionFactory.

public class AdaptiveExtensionFactory implements ExtensionFactory {

    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        //獲取該拓展點(diǎn)的所有實(shí)現(xiàn)類的名稱,并進(jìn)行遍歷
        for (String name : loader.getSupportedExtensions()) {
            //往list里面添加根據(jù)name獲取到的拓展點(diǎn)實(shí)例
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        //遍歷所有的ExtensionFactory撒轮,當(dāng)獲取到的extension不為空的時(shí)候乞旦,則返回結(jié)果;
        //如果是SPI拓展點(diǎn)题山,這里最后調(diào)用SpiExtensionFactory.getExtension兰粉,然后調(diào)用ExtensionLoader.getAdaptiveExtension方法返回拓展點(diǎn)實(shí)例
        //如果非SPI拓展點(diǎn),這里通過(guò)SpringExtensionFactory獲取
        // SpringExtensionFactory不支持SPI拓展點(diǎn)實(shí)例的獲取顶瞳,詳見SpringExtensionFactory的getExtension方法
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}

首先看AdaptiveExtensionFactory的構(gòu)造方法玖姑,獲取ExtensionFactory的所有拓展點(diǎn)實(shí)現(xiàn)類,然后使用list列表factories存儲(chǔ)慨菱,這里list的內(nèi)容為SpiExtensionFactory焰络、SpringExtensionFactory,如圖

1539343310182.png

再看AdaptiveExtensionFactory.getExtension(Class<T> type, String name):

  • 遍歷factories
  • 通過(guò)獲取到的ObjectFactory.getExtension符喝,獲取到對(duì)應(yīng)拓展點(diǎn)實(shí)例
    • 如果是傳入的類型是SPI拓展點(diǎn)闪彼,那么通過(guò)SpiExtensionFactory獲取,返回值被@Adaptive修飾的拓展點(diǎn)或者動(dòng)態(tài)自適應(yīng)拓展點(diǎn)

      1539348046129.png

    • 如果傳入類型非SPI拓展點(diǎn)洲劣,通過(guò)SpringExtensionFactory獲取备蚓,從IOC容器中獲取

      1539348577083.png

到這里,注入的邏輯也分析結(jié)束囱稽,總結(jié)下注入的邏輯:

  • 如果當(dāng)前typeExtensionFactory類型郊尝,則可以進(jìn)行注入
  • ExtensionLoaderobjectFactoryAdaptiveExtensionFactory
  • 如果注入的類型是SPI修飾的接口,那么獲取到的值為@Adaptive修飾的拓展點(diǎn)或者動(dòng)態(tài)自適應(yīng)拓展點(diǎn)
  • 如果注入的類型非SPI修飾的接口战惊,那么獲取的值是通過(guò)name或者注入類型type從IOC容器中獲取

總結(jié)

走讀了以上代碼流昏,對(duì)此過(guò)程做個(gè)總結(jié):

  • 實(shí)例化ExtensionLoader,對(duì)當(dāng)前ExtensionLoaderobjectFactory設(shè)值為AdaptiveExtensionFactory,以注冊(cè)式單例存儲(chǔ)在緩存EXTENSION_LOADERS1,類型為ConcurrentMap<Class<?>, ExtensionLoader<?>>
  • 加載當(dāng)前拓展點(diǎn)實(shí)現(xiàn)類况凉,讀取指定文件下的指定文件谚鄙,分為三種類型的class的加載:
    • @Adaptive修飾的實(shí)現(xiàn)類的加載,存儲(chǔ)在緩存cachedAdaptiveClass
    • 包裝類刁绒,存儲(chǔ)在Set緩存cachedWrapperClasses
    • 普通的實(shí)現(xiàn)類(包含@Active修飾的闷营,且用緩存cachedActivates存儲(chǔ),形式為name->Active)存儲(chǔ)在cachedClasses知市,存儲(chǔ)形式為name->class,且為注冊(cè)式單例存儲(chǔ)
  • 如果當(dāng)前拓展點(diǎn)有@Adaptive修飾的實(shí)現(xiàn)類,則返回該實(shí)現(xiàn)類的實(shí)例傻盟,且存儲(chǔ)在緩存cachedAdaptiveClass,該值為單例,不可修改
  • 如果當(dāng)前拓展點(diǎn)沒(méi)有@Adaptive修飾的實(shí)現(xiàn)類,返回動(dòng)態(tài)自適應(yīng)拓展點(diǎn)實(shí)現(xiàn)類嫂丙,例如Protocol的動(dòng)態(tài)自適應(yīng)拓展點(diǎn)實(shí)現(xiàn)類為Protocol$Adaptive,該拓展點(diǎn)的方法調(diào)用其實(shí)是根據(jù)傳入Urlprotocol的類型來(lái)獲取具體的拓展點(diǎn)實(shí)現(xiàn)類娘赴,如配置為dubbo協(xié)議的拓展點(diǎn)實(shí)現(xiàn)類為DubboProtocol

參考

擴(kuò)展點(diǎn)加載

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市跟啤,隨后出現(xiàn)的幾起案子诽表,更是在濱河造成了極大的恐慌,老刑警劉巖隅肥,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件竿奏,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡腥放,警方通過(guò)查閱死者的電腦和手機(jī)议双,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)捉片,“玉大人,你說(shuō)我怎么就攤上這事汞舱∥槿遥” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵昂芜,是天一觀的道長(zhǎng)莹规。 經(jīng)常有香客問(wèn)我,道長(zhǎng)泌神,這世上最難降的妖魔是什么良漱? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮欢际,結(jié)果婚禮上母市,老公的妹妹穿的比我還像新娘。我一直安慰自己损趋,他們只是感情好患久,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般蒋失。 火紅的嫁衣襯著肌膚如雪返帕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天篙挽,我揣著相機(jī)與錄音荆萤,去河邊找鬼。 笑死铣卡,一個(gè)胖子當(dāng)著我的面吹牛链韭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播算行,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼梧油,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了州邢?” 一聲冷哼從身側(cè)響起儡陨,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎量淌,沒(méi)想到半個(gè)月后骗村,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡呀枢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年胚股,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了琅拌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片党晋。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扳剿,死狀恐怖轻庆,靈堂內(nèi)的尸體忽然破棺而出夸盟,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布行疏,位于F島的核電站夺巩,受9級(jí)特大地震影響征绎,放射性物質(zhì)發(fā)生泄漏忙厌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一挟阻、第九天 我趴在偏房一處隱蔽的房頂上張望坷备。 院中可真熱鬧情臭,春花似錦省撑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至朝巫,卻和暖如春鸿摇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背劈猿。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工拙吉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人揪荣。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓筷黔,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親仗颈。 傳聞我的和親對(duì)象是個(gè)殘疾皇子佛舱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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

  • 一.概覽 整體描述 dubbo利用spi擴(kuò)展機(jī)制實(shí)現(xiàn)大量的動(dòng)態(tài)擴(kuò)展,要想充分了解dubbo的擴(kuò)展機(jī)制挨决,首先必須弄明...
    致慮閱讀 897評(píng)論 0 2
  • Dubbo采用微內(nèi)核+插件體系请祖,使得設(shè)計(jì)優(yōu)雅,擴(kuò)展性強(qiáng)脖祈。那所謂的微內(nèi)核+插件體系是如何實(shí)現(xiàn)的呢肆捕!大家是否熟悉spi...
    carl_zhao閱讀 936評(píng)論 1 3
  • 從上一篇 Java SPI 機(jī)制解析 可以知道 Java SPI 的一些劣勢(shì)。Dubbo 的擴(kuò)展點(diǎn)加載從 Java...
    匠丶閱讀 4,179評(píng)論 0 7
  • 前面我們了解過(guò)了Java的SPI擴(kuò)展機(jī)制盖高,對(duì)于Java擴(kuò)展機(jī)制的原理以及優(yōu)缺點(diǎn)也有了大概的了解慎陵,這里繼續(xù)深入一下D...
    加大裝益達(dá)閱讀 5,059評(píng)論 2 20
  • 0 前言 站在一個(gè)框架作者的角度來(lái)說(shuō)眼虱,定義一個(gè)接口,自己默認(rèn)給出幾個(gè)接口的實(shí)現(xiàn)類席纽,同時(shí) 允許框架的使用者也能夠自定...
    七寸知架構(gòu)閱讀 16,238評(píng)論 3 67