Dubbo SPI之Adaptive詳解

前期準(zhǔn)備

一. 增加pom

<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>dubbo</artifactId>
   <version>2.5.3</version>
</dependency>

二. 添加代碼

1. shuqi.dubbotest.spi.adaptive.AdaptiveExt2 作為需要被擴(kuò)展的接口,注意要加上@SPI注解

package shuqi.dubbotest.spi.adaptive;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.Adaptive;
import com.alibaba.dubbo.common.extension.SPI;

/**
 * @author linyang on 18/4/20.
 */
@SPI
public interface AdaptiveExt2 {
    @Adaptive
    String echo(String msg, URL url);
}

2. 上面接口的三個實(shí)現(xiàn)類

a. shuqi.dubbotest.spi.adaptive.DubboAdaptiveExt2
package shuqi.dubbotest.spi.adaptive;

import com.alibaba.dubbo.common.URL;

/**
 * @author linyang on 18/4/20.
 */
public class DubboAdaptiveExt2 implements AdaptiveExt2 {

    public String echo(String msg, URL url) {
        return "dubbo";
    }
}
b. shuqi.dubbotest.spi.adaptive.SpringCloudAdaptiveExt2
package shuqi.dubbotest.spi.adaptive;

import com.alibaba.dubbo.common.URL;

/**
 * @author linyang on 18/4/20.
 */
public class SpringCloudAdaptiveExt2 implements AdaptiveExt2 {

    public String echo(String msg, URL url) {
        return "spring cloud";
    }
}
c. shuqi.dubbotest.spi.adaptive.ThriftAdaptiveExt2
package shuqi.dubbotest.spi.adaptive;

import com.alibaba.dubbo.common.URL;

/**
 * @author linyang on 18/4/20.
 */
public class ThriftAdaptiveExt2 implements AdaptiveExt2 {

    public String echo(String msg, URL url) {
        return "thrift";
    }
}

3. 在Resource目錄下浪藻,添加/META-INF/dubbo/internal/shuqi.dubbotest.spi.adaptive.AdaptiveExt2文件,里面的內(nèi)容

dubbo=shuqi.dubbotest.spi.adaptive.DubboAdaptiveExt2
cloud=shuqi.dubbotest.spi.adaptive.SpringCloudAdaptiveExt2
thrift=shuqi.dubbotest.spi.adaptive.ThriftAdaptiveExt2

上車 just do it!

測試一:SPI注解中有value值

@SPI("dubbo")
public interface AdaptiveExt2 {
    @Adaptive
    String echo(String msg, URL url);
}
    @Test
    public void test1() {
        ExtensionLoader<AdaptiveExt2> loader = ExtensionLoader.getExtensionLoader(AdaptiveExt2.class);
        AdaptiveExt2 adaptiveExtension = loader.getAdaptiveExtension();
        URL url = URL.valueOf("test://localhost/test");
        System.out.println(adaptiveExtension.echo("d", url));
    }
dubbo

測試二:SPI注解中有value值,URL中也有具體的值

@SPI("dubbo")
public interface AdaptiveExt2 {
    @Adaptive
    String echo(String msg, URL url);
}
    @Test
    public void test2() {
        ExtensionLoader<AdaptiveExt2> loader = ExtensionLoader.getExtensionLoader(AdaptiveExt2.class);
        AdaptiveExt2 adaptiveExtension = loader.getAdaptiveExtension();
        URL url = URL.valueOf("test://localhost/test?adaptive.ext2=cloud");
        System.out.println(adaptiveExtension.echo("d", url));
    }
spring cloud

測試三:SPI注解中有value值段只,URL中也有具體的值,實(shí)現(xiàn)類上有@Adaptive注解

為ThriftAdaptiveExt2類添加@Adaptive注解

@Adaptive
public class ThriftAdaptiveExt2 implements AdaptiveExt2 {
    public String echo(String msg, URL url) {
        return "thrift";
    }
}
@SPI("dubbo")
public interface AdaptiveExt2 {
    @Adaptive
    String echo(String msg, URL url);
}
    @Test
    public void test1() {
        ExtensionLoader<AdaptiveExt2> loader = ExtensionLoader.getExtensionLoader(AdaptiveExt2.class);
        AdaptiveExt2 adaptiveExtension = loader.getAdaptiveExtension();
        URL url = URL.valueOf("test://localhost/test?adaptive.ext2=cloud");
        System.out.println(adaptiveExtension.echo("d", url));
    }
thrift

測試四:SPI注解中有value值,實(shí)現(xiàn)類上沒有@Adaptive注解,在方法上打上@Adaptive注解,注解中的value與鏈接中的參數(shù)的key一致肝匆,鏈接中的key對應(yīng)的value就是spi中的name,獲取相應(yīng)的實(shí)現(xiàn)類。

@SPI("dubbo")
public interface AdaptiveExt2 {   
    @Adaptive({"t"})
    String echo(String msg, URL url);
}
    @Test
    public void test1() {
        ExtensionLoader<AdaptiveExt2> loader = ExtensionLoader.getExtensionLoader(AdaptiveExt2.class);
        AdaptiveExt2 adaptiveExtension = loader.getAdaptiveExtension();
        URL url = URL.valueOf("test://localhost/test?t=cloud");
        System.out.println(adaptiveExtension.echo("d", url));
    }
cloud

結(jié)論:

從上面的幾個測試用例顺献,可以得到下面的結(jié)論:1. 在類上加上@Adaptive注解的類旗国,是最為明確的創(chuàng)建對應(yīng)類型Adaptive類。所以他優(yōu)先級最高滚澜。2. @SPI注解中的value是默認(rèn)值粗仓,如果通過URL獲取不到關(guān)于取哪個類作為Adaptive類的話,就使用這個默認(rèn)值设捐,當(dāng)然如果URL中可以獲取到借浊,就用URL中的。3. 可以再方法上增加@Adaptive注解萝招,注解中的value與鏈接中的參數(shù)的key一致蚂斤,鏈接中的key對應(yīng)的value就是spi中的name,獲取相應(yīng)的實(shí)現(xiàn)類。

源碼分析

下面我們帶著上面的結(jié)論槐沼,看一下源碼曙蒸。首先我們從這句話開始講起

ExtensionLoader<AdaptiveExt2> loader = ExtensionLoader.getExtensionLoader(AdaptiveExt2.class);

下面是源碼的注釋

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!");
        }
        /**
         * 這個類型必須加上SPI注解捌治,否則報錯
         */
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }
        /**
         * 從緩存中獲取
         */
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            /**
             * 取不到創(chuàng)建一個放入EXTENSION_LOADERS中
             */
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

先檢查有沒有帶SPI的注解,沒有帶纽窟,直接報錯肖油,從緩存中根據(jù)這個類型查詢對應(yīng)的ExtensionLoader,查不到就創(chuàng)建一個臂港,再放入緩存中森枪。dubbo中的spi部分大量利用了本地緩存,后續(xù)出現(xiàn)审孽,不再著重講解了县袱。我們可以看一下他的創(chuàng)建該類型的ExtensionLoader的方法。

private ExtensionLoader(Class<?> type) {
        this.type = type;
        /**
         * type如果是ExtensionFactory類型佑力,那么objectFactory是null,否則是ExtensionFactory類型的適配器類型
         */
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

關(guān)注點(diǎn)有兩個式散,第一個構(gòu)造方法是私有的,說明不想通過外部實(shí)例化打颤,將實(shí)例化的過程統(tǒng)一收緊暴拄。第二個是objectFactory這個在后面的ioc部分會發(fā)揮它的作用,敬請期待瘸洛。好了揍移,目前為止,ExtensionLoader<AdaptiveExt2> loader = ExtensionLoader.getExtensionLoader(AdaptiveExt2.class) 說的差不多了反肋,下面進(jìn)入我們的大頭getAdaptiveExtension

 public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if(createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            //緩存中獲取不到就創(chuàng)建
                            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 {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

獲取到適配器類的Class,利用反射創(chuàng)建適配器類的實(shí)例。injectExtension是dubbo的DI踏施,依賴注入石蔗。如果適配器類有屬性的set方法,會自動注入畅形,這個后續(xù)會開一個章節(jié)進(jìn)行解釋养距。看來我們最終要關(guān)注的是getAdaptiveExtensionClass方法日熬。大家跟緊了棍厌,大片開始了。

    private Class<?> getAdaptiveExtensionClass() {
        /**
         * 觸發(fā)SPI流程的掃描
         */
        getExtensionClasses();
        /**
         * 如果通過上面的步驟可以獲取到cachedAdaptiveClass直接返回竖席,如果不行的話耘纱,就得考慮自己進(jìn)行利用動態(tài)代理創(chuàng)建一個了
         */
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        /**
         * 利用動態(tài)代理創(chuàng)建一個擴(kuò)展類
         */
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

按照順序來吧,看下getExtensionClasses

    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;
    }

從緩存中取毕荐,也就是說束析,加載的流程只觸發(fā)一次,然后放入緩存憎亚,后續(xù)從緩存取员寇。

    private Map<String, Class<?>> loadExtensionClasses() {
        /**
         * 獲取到類型的SPI注解弄慰,所以利用SPI擴(kuò)展點(diǎn)的地方,需要加入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));
                }
                /**
                 * 如果注解中有value蝶锋,說明有默認(rèn)的實(shí)現(xiàn)陆爽,那么將value放到cachedDefaultName中
                 */
                if (names.length == 1) cachedDefaultName = names[0];
            }
        }

        /**
         * 從下面的地址中加在這個類型的數(shù)據(jù)的extensionClasses中,地址包括
         * META-INF/dubbo/internal/
         * META-INF/dubbo/
         * META-INF/services/
         */
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

獲取type上的SPI注解扳缕,如果里面有值賦給cachedDefaultName這個變量慌闭,相當(dāng)于是個默認(rèn)的值。隨后從META-INF/dubbo/internal/第献,META-INF/dubbo/贡必,META-INF/services/這三個路徑下搜索對應(yīng)的文件,什么是對應(yīng)的庸毫?就是命名是這個type類的全限定名稱仔拟。下面這個方法有點(diǎn)長,希望不要看吐飒赃,還好加了注解利花,希望看起來會通暢點(diǎn)。


private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
        String fileName = dir + type.getName();
        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 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)容是我們需要的數(shù)據(jù)载佳,后面的內(nèi)容可以認(rèn)為是注釋之類的
                                 */
                                final int ci = line.indexOf('#');
                                if (ci >= 0) line = line.substring(0, ci);
                                line = line.trim();
                                if (line.length() > 0) {
                                    try {
                                        String name = null;
                                        /**
                                         * 看下=的位置炒事,如果有等號,那么=前面的數(shù)據(jù)是名稱蔫慧,后面的要實(shí)現(xiàn)的類的全路徑名稱
                                         */
                                        int i = line.indexOf('=');
                                        if (i > 0) {
                                            name = line.substring(0, i).trim();
                                            line = line.substring(i + 1).trim();
                                        }
                                        if (line.length() > 0) {
                                            /**
                                             * 加載解析出來的類的全限定名稱
                                             */
                                            Class<?> clazz = Class.forName(line, true, classLoader);
                                            /**
                                             * 判斷是新加載clazz是否是type的子類挠乳,不是報錯
                                             */
                                            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) {
                                                    /**
                                                     * 將此類作為cachedAdaptiveClass
                                                     */
                                                    cachedAdaptiveClass = clazz;
                                                }
                                                /**
                                                 * 多個adaptive類的實(shí)例睡扬,報錯
                                                 */
                                                else if (!cachedAdaptiveClass.equals(clazz)) {
                                                    throw new IllegalStateException("More than 1 adaptive class found: "
                                                            + cachedAdaptiveClass.getClass().getName()
                                                            + ", " + clazz.getClass().getName());
                                                }
                                            }
                                            /**
                                             * 如果這個類,沒有Adaptive注解
                                             */
                                            else {
                                                try {
                                                    /**
                                                     * 看看這個類黍析,有沒有此類型的構(gòu)造方法卖怜,主要是Wrapper類使用,如果有阐枣,說明是個wrapper類
                                                     */
                                                    clazz.getConstructor(type);
                                                    Set<Class<?>> wrappers = cachedWrapperClasses;
                                                    if (wrappers == null) {
                                                        cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                                                        wrappers = cachedWrapperClasses;
                                                    }
                                                    /**
                                                     * 將此wrapper放入wrappers容器里面马靠,也就是cachedWrapperClasses里面
                                                     */
                                                    wrappers.add(clazz);
                                                } catch (NoSuchMethodException e) {
                                                    /**
                                                     * 如果不是wapper類型的數(shù)據(jù),肯定會拋異常蔼两,被捕獲甩鳄,到了這里,說明是個正常的類
                                                     */
                                                    clazz.getConstructor();
                                                    /**
                                                     * 如果name沒有寫的話宪哩,就使用默認(rèn)的規(guī)則生成一個
                                                     */
                                                    if (name == null || name.length() == 0) {
                                                        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);
                                                            }
                                                        }
                                                    }
                                                    String[] names = NAME_SEPARATOR.split(name);
                                                    if (names != null && names.length > 0) {
                                                        /**
                                                         * 看下這個類上有沒有Activate注解
                                                         */
                                                        Activate activate = clazz.getAnnotation(Activate.class);
                                                        if (activate != null) {
                                                            //存在娩贷,就往cachedActivates里面添加名稱與注解
                                                            cachedActivates.put(names[0], activate);
                                                        }
                                                        for (String n : names) {
                                                            if (!cachedNames.containsKey(clazz)) {
                                                                //將此類型的實(shí)例與name放入cachedNames
                                                                cachedNames.put(clazz, n);
                                                            }
                                                            Class<?> c = extensionClasses.get(n);
                                                            if (c == null) {
                                                                //最后往extensionClasses添加名稱與class
                                                                extensionClasses.put(n, clazz);
                                                            } 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);
        }
    }

路徑+文件名,組成了一個具體的文件名锁孟,根據(jù) classLoader.getResources 方法獲取到Enumeration<java.net.URL> 類型的對象彬祖,隨后就是遍歷,文件里面的格式是這樣的茁瘦。

dubbo=shuqi.dubbotest.spi.adaptive.DubboAdaptiveExt2
cloud=shuqi.dubbotest.spi.adaptive.SpringCloudAdaptiveExt2
thrift=shuqi.dubbotest.spi.adaptive.ThriftAdaptiveExt2

如果有#,按照#分割储笑,前面的是我們需要的業(yè)務(wù)數(shù)據(jù)甜熔,后面是注釋。我們所需要的數(shù)據(jù)中如果有=,那么就按照=進(jìn)行分割,前面是這個擴(kuò)展的名稱突倍,后面是這個擴(kuò)展的全限定類名腔稀,方便利用反射加載進(jìn)來。加載進(jìn)來之后會判斷下是不是傳入類(type)類型的實(shí)例羽历,不是的話焊虏,報錯!如果有的類上帶有@Adaptive注解秕磷,那么將這個類賦值給cachedAdaptiveClass诵闭,注意這個點(diǎn),查詢type類型適配器類的時候會優(yōu)先尋找cachedAdaptiveClass澎嚣,因?yàn)槭窍到y(tǒng)指定的適配器類疏尿,優(yōu)先級最高,可以看下我們上面的測試三易桃,說的就是這種情況褥琐。如果有多個實(shí)現(xiàn)再類上都打上了@Adaptive注解,會報錯:標(biāo)準(zhǔn)的適配器類只能有一個晤郑。如果這個擴(kuò)展類沒有打上@Adaptive注解就更有意思了敌呈。首先第一步會驗(yàn)證下有沒有type這個類型作為入?yún)⒌臉?gòu)造方法,為什么要這么做造寝?因?yàn)閃rapper驱富,有的類型需要包裝一下,例如type=Protocol.class 就會看到有DubboProtocol真實(shí)的Protocal類,還會有ProtocolFilterWrapper和ProtocolListenerWrapper這種Wrapper類匹舞,這種Wrapper類的共同點(diǎn)就是構(gòu)造函數(shù)的入?yún)⑹莟ype類型,所以在解析的時候有這么一步线脚。如果有這種構(gòu)造函數(shù)的就是Warpper類赐稽,將這些Warpper類型的數(shù)據(jù)放到cachedWrapperClasses這個集合中緩存。如果沒有這種類型的構(gòu)造函數(shù)浑侥,就是正常的type類型的實(shí)例了姊舵,如果在文件中沒有聲明這個擴(kuò)展的名稱(=左邊的部分),就會根據(jù)這個類名創(chuàng)建一個名稱寓落。然后進(jìn)入下一個環(huán)節(jié)@Activate數(shù)據(jù)的解析括丁,這個本來是下一節(jié)的內(nèi)容,我們提前了解下吧伶选。查看type類上有沒有@Activate注解史飞,如果有的話尖昏,將名稱與注解放到cachedActivates這個Map中進(jìn)行緩存。將擴(kuò)展類和名稱放入cachedNames這個Map中進(jìn)行緩存构资,將名稱和擴(kuò)展類的class放入傳遞進(jìn)來的extensionClasses中抽诉,最后這個extensionClasses會被返回出來被使用。OK吐绵,到目前為止我們結(jié)束了getExtensionClasses方法的講解迹淌,是不是很繞,東西很多己单。再回來我們看下剩下的唉窃。

private Class<?> getAdaptiveExtensionClass() {
        /**
         * 觸發(fā)SPI流程的掃描
         */
        getExtensionClasses();
        /**
         * 如果通過上面的步驟可以獲取到cachedAdaptiveClass直接返回,如果不行的話纹笼,就得考慮自己進(jìn)行利用動態(tài)代理創(chuàng)建一個了
         */
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        /**
         * 利用動態(tài)代理創(chuàng)建一個擴(kuò)展類
         */
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

如果cachedAdaptiveClass不為空就返回纹份,什么情況下不為空?當(dāng)擴(kuò)展類上打上@Adaptive注解的時候允乐,就會將這個類直接返回矮嫉。如果沒有上注解,怎么辦牍疏,就得自己生成了蠢笋,也就是createAdaptiveExtensionClass

    private Class<?> createAdaptiveExtensionClass() {
        /**
         * 創(chuàng)建代碼的字符串形式
         */
        String code = createAdaptiveExtensionClassCode();
        /**
         * 尋找類加載器
         */
        ClassLoader classLoader = findClassLoader();
        /**
         * 尋找Compiler的適配器擴(kuò)展類
         */
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        /**
         * 進(jìn)行編譯成Class的實(shí)例返回
         */
        return compiler.compile(code, classLoader);
    }

思路很簡單,將類以字符串的形式拼接出來鳞陨,然后利用編譯器進(jìn)行編譯昨寞,返回編譯后的class對象。尋找編譯器的過程和具體編譯的過程不是我們此次所要關(guān)心的厦滤,我們關(guān)心的是這個createAdaptiveExtensionClassCode方法創(chuàng)建的字符串格式的數(shù)據(jù)是啥樣的援岩,用到了哪些數(shù)據(jù)。又是一個大方法掏导,也是要走起的享怀,come on

 /**
     * 創(chuàng)建適配器的擴(kuò)展類的String
     * <p>
     * <p>
     * 創(chuàng)建這個適配器的擴(kuò)展類,有幾個前提:
     * 1. 必須有SPI的注解
     * 2. 被SPI聲明的接口中至少一個方法有Adaptive注解趟咆。
     * ProxyFactory
     * 下面是他的說明:
     * 當(dāng)聲明再方法上的Adaptive中的value的作用就是添瓷,從URL中獲取key,value,例如ProxyFactory
     *
     * @return
     * @Adaptive({Constants.PROXY_KEY})==========="proxy" <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;
     * <p>
     * 如果URL中是dubbo:xxxx?proxy=jdk,而SPI中的值是javassist,那么就是
     * <p>
     * String extName = url.getParameter("proxy", "javassist");  //結(jié)果是jdk
     */
    private String createAdaptiveExtensionClassCode() {
        StringBuilder codeBuidler = new StringBuilder();
        /**
         * 獲取這個類型的所有的方法
         */
        Method[] methods = type.getMethods();
        boolean hasAdaptiveAnnotation = false;
        /**
         * 遍歷所有方法,至少有一個方法打了Adaptive的注解值纱,否則報錯
         */
        for (Method m : methods) {
            if (m.isAnnotationPresent(Adaptive.class)) {
                hasAdaptiveAnnotation = true;
                break;
            }
        }
        // no need to generate adaptive class since there's no adaptive method found.
        if (!hasAdaptiveAnnotation)
            throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");

        codeBuidler.append("package " + type.getPackage().getName() + ";");
        codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");
        codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adaptive" + " implements " + type.getCanonicalName() + " {");

        for (Method method : methods) {
            Class<?> rt = method.getReturnType();
            /**
             * 參數(shù)列表的類型
             */
            Class<?>[] pts = method.getParameterTypes();
            /**
             * 異常列表的類型
             */
            Class<?>[] ets = method.getExceptionTypes();
            /**
             * 獲得Adaptive的注解
             */
            Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
            StringBuilder code = new StringBuilder(512);
            /**
             * 如果這個方法沒有注解鳞贷,添加不支持調(diào)用此方法的異常
             */
            if (adaptiveAnnotation == null) {
                code.append("throw new UnsupportedOperationException(\"method ")
                        .append(method.toString()).append(" of interface ")
                        .append(type.getName()).append(" is not adaptive method!\");");
            } else {
                int urlTypeIndex = -1;
                /**
                 * 尋找列表中的類型是URL.class,記錄他的位置虐唠,數(shù)據(jù)放到urlTypeIndex中
                 */
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].equals(URL.class)) {
                        urlTypeIndex = i;
                        break;
                    }
                }
                /**
                 * 找到了URL類型的參數(shù)
                 * */
                if (urlTypeIndex != -1) {
                    // Null Point check
                    String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
                            urlTypeIndex);
                    code.append(s);

                    s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex);
                    code.append(s);
                }
                // did not find parameter in URL type
                else {
                    String attribMethod = null;

                    // find URL getter method
                    LBL_PTS:
                    for (int i = 0; i < pts.length; ++i) {
                        Method[] ms = pts[i].getMethods();
                        for (Method m : ms) {
                            String name = m.getName();
                            if ((name.startsWith("get") || name.length() > 3)
                                    && Modifier.isPublic(m.getModifiers())
                                    && !Modifier.isStatic(m.getModifiers())
                                    && m.getParameterTypes().length == 0
                                    && m.getReturnType() == URL.class) {
                                urlTypeIndex = i;
                                attribMethod = name;
                                break LBL_PTS;
                            }
                        }
                    }
                    if (attribMethod == null) {
                        throw new IllegalStateException("fail to create adaptive class for interface " + type.getName()
                                + ": not found url parameter or url attribute in parameters of method " + method.getName());
                    }

                    // Null point check
                    String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
                            urlTypeIndex, pts[urlTypeIndex].getName());
                    code.append(s);
                    s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
                            urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
                    code.append(s);

                    s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod);
                    code.append(s);
                }

                /**
                 * 獲取adaptive注解的value
                 */
                String[] value = adaptiveAnnotation.value();
                /**
                 * 如果value沒有設(shè)置搀愧,那么將使用類的名稱作為key
                 */
                if (value.length == 0) {
                    char[] charArray = type.getSimpleName().toCharArray();
                    StringBuilder sb = new StringBuilder(128);
                    for (int i = 0; i < charArray.length; i++) {
                        if (Character.isUpperCase(charArray[i])) {
                            if (i != 0) {
                                sb.append(".");
                            }
                            sb.append(Character.toLowerCase(charArray[i]));
                        } else {
                            sb.append(charArray[i]);
                        }
                    }
                    value = new String[]{sb.toString()};
                }

                boolean hasInvocation = false;
                /**
                 * 如果參數(shù)列表中有Invocation的實(shí)例
                 */
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
                        // Null Point check
                        String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
                        code.append(s);
                        s = String.format("\nString methodName = arg%d.getMethodName();", i);
                        code.append(s);
                        hasInvocation = true;
                        break;
                    }
                }

                /**
                 * defaultExtName為spi注解中的value
                 */
                String defaultExtName = cachedDefaultName;
                String getNameCode = null;
                for (int i = value.length - 1; i >= 0; --i) {
                    if (i == value.length - 1) {
                        /**
                         * 如果defaultExtName 存在,spi注解中的value存在
                         */
                        if (null != defaultExtName) {
                            /**
                             * value[i]的值不等于"protocol"
                             */
                            if (!"protocol".equals(value[i])) {
                                if (hasInvocation) {
                                    getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);

                                } else {
                                    /**
                                     *     value[i]="proxy"
                                     *     defaultExtName="javassist"
                                     *
                                     *    String extName = url.getParameter("proxy", "javassist");
                                     */
                                    getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);

                                }
                            } else {
                                getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
                            }
                        } else {
                            /**
                             * 如果defaultExtName 不存在,spi注解中的value不存在
                             */
                            if (!"protocol".equals(value[i])) {
                                if (hasInvocation) {
                                    getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                                } else {
                                    /**
                                     * url.getParameter("proxy") 沒有默認(rèn)值
                                     */
                                    getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
                                }
                            } else {
                                getNameCode = "url.getProtocol()";

                            }
                        }
                    } else {
                        if (!"protocol".equals(value[i]))
                            if (hasInvocation)
                                getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                            else
                                getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
                        else
                            getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
                    }
                }
                code.append("\nString extName = ").append(getNameCode).append(";");
                // check extName == null?
                String s = String.format("\nif(extName == null) " +
                                "throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",
                        type.getName(), Arrays.toString(value));
                code.append(s);

                s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
                        type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
                code.append(s);

                // return statement
                if (!rt.equals(void.class)) {
                    code.append("\nreturn ");
                }

                s = String.format("extension.%s(", method.getName());
                code.append(s);
                for (int i = 0; i < pts.length; i++) {
                    if (i != 0)
                        code.append(", ");
                    code.append("arg").append(i);
                }
                code.append(");");
            }

            codeBuidler.append("\npublic " + rt.getCanonicalName() + " " + method.getName() + "(");
            for (int i = 0; i < pts.length; i++) {
                if (i > 0) {
                    codeBuidler.append(", ");
                }
                codeBuidler.append(pts[i].getCanonicalName());
                codeBuidler.append(" ");
                codeBuidler.append("arg" + i);
            }
            codeBuidler.append(")");
            if (ets.length > 0) {
                codeBuidler.append(" throws ");
                for (int i = 0; i < ets.length; i++) {
                    if (i > 0) {
                        codeBuidler.append(", ");
                    }
                    codeBuidler.append(ets[i].getCanonicalName());
                }
            }
            codeBuidler.append(" {");
            codeBuidler.append(code.toString());
            codeBuidler.append("\n}");
        }
        codeBuidler.append("\n}");
        if (logger.isDebugEnabled()) {
            logger.debug(codeBuidler.toString());
        }
        return codeBuidler.toString();
    }

首先尋找這個類中所有的方法咱筛,查看方法中有沒有打@Adaptive注解的搓幌,一個沒有,直接報錯眷蚓!對于那些沒有加@Adaptive注解的方法鼻种,直接在要創(chuàng)建的Adaptive類上增加此方法不支持操作的異常。在方法中的@Adaptive是可以加上value值的沙热,如果用戶填了叉钥,使用此值,沒有填將使用程序根據(jù)類名創(chuàng)建的值作為value值篙贸,這個value值通URL中的參數(shù)名保持一致投队。defaultExtName是SPI中的value值,這里可以看一下我們的測試四的方法爵川。最后我們看一下敷鸦,他生成的String是什么樣子的

package shuqi.dubbotest.d;

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

public class AdaptiveExt2$Adpative implements shuqi.dubbotest.spi.adaptive.AdaptiveExt2 {
    public java.lang.String echo(java.lang.String arg0, com.alibaba.dubbo.common.URL arg1) {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = url.getParameter("t", "dubbo");
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(shuqi.dubbotest.spi.adaptive.AdaptiveExt2) name from url(" + url.toString() + ") use keys([t])");
        shuqi.dubbotest.spi.adaptive.AdaptiveExt2 extension = (shuqi.dubbotest.spi.adaptive.AdaptiveExt2) ExtensionLoader.getExtensionLoader(shuqi.dubbotest.spi.adaptive.AdaptiveExt2.class).getExtension(extName);
        return extension.echo(arg0, arg1);
    }
}

將上面這個字符串編譯成Class對象,作為適配器類寝贡,返回扒披,然后實(shí)例化后,進(jìn)行依賴注入需要的屬性圃泡,隨后緩存碟案,備下次使用。

適用場景

基本上所有類型的動態(tài)導(dǎo)入都是使用adaptive,使用范圍極廣颇蜡。

測試源碼

dubbo-test測試源碼

預(yù)告价说,看這里

下一篇: Dubbo SPI 之Activate詳解

END

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市风秤,隨后出現(xiàn)的幾起案子鳖目,更是在濱河造成了極大的恐慌,老刑警劉巖缤弦,帶你破解...
    沈念sama閱讀 212,222評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件领迈,死亡現(xiàn)場離奇詭異,居然都是意外死亡碍沐,警方通過查閱死者的電腦和手機(jī)惦费,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抢韭,“玉大人,你說我怎么就攤上這事恍箭】坦В” “怎么了?”我有些...
    開封第一講書人閱讀 157,720評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鳍贾。 經(jīng)常有香客問我鞍匾,道長,這世上最難降的妖魔是什么骑科? 我笑而不...
    開封第一講書人閱讀 56,568評論 1 284
  • 正文 為了忘掉前任橡淑,我火速辦了婚禮,結(jié)果婚禮上咆爽,老公的妹妹穿的比我還像新娘梁棠。我一直安慰自己,他們只是感情好斗埂,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,696評論 6 386
  • 文/花漫 我一把揭開白布符糊。 她就那樣靜靜地躺著,像睡著了一般呛凶。 火紅的嫁衣襯著肌膚如雪男娄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,879評論 1 290
  • 那天漾稀,我揣著相機(jī)與錄音模闲,去河邊找鬼。 笑死崭捍,一個胖子當(dāng)著我的面吹牛尸折,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缕贡,決...
    沈念sama閱讀 39,028評論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼翁授,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了晾咪?” 一聲冷哼從身側(cè)響起收擦,我...
    開封第一講書人閱讀 37,773評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谍倦,沒想到半個月后塞赂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,220評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昼蛀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,550評論 2 327
  • 正文 我和宋清朗相戀三年宴猾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叼旋。...
    茶點(diǎn)故事閱讀 38,697評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡仇哆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出夫植,到底是詐尸還是另有隱情讹剔,我是刑警寧澤油讯,帶...
    沈念sama閱讀 34,360評論 4 332
  • 正文 年R本政府宣布,位于F島的核電站延欠,受9級特大地震影響陌兑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜由捎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,002評論 3 315
  • 文/蒙蒙 一兔综、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狞玛,春花似錦软驰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蒙畴,卻和暖如春贰镣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背膳凝。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評論 1 266
  • 我被黑心中介騙來泰國打工碑隆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蹬音。 一個月前我還...
    沈念sama閱讀 46,433評論 2 360
  • 正文 我出身青樓上煤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親著淆。 傳聞我的和親對象是個殘疾皇子劫狠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,587評論 2 350

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