Dubbo擴展點機制分析(二)

1、擴展點分析之ExtensionLoader

    Dubbo的擴展點框架主要位於com.alibaba.dubbo.common.extension這個包下,其結構以下:java

com.alibaba.dubbo.common.extension
 |
 |--factory
 |     |--AdaptiveExtensionFactory   
 |     |--SpiExtensionFactory        
 |
 |--support
 |     |--ActivateComparator
 |
 |--Activate  #自動激活加載擴展的註解
 |--Adaptive  #自適應擴展點的註解
 |--ExtensionFactory  #擴展點對象生成工廠接口
 |--ExtensionLoader   #擴展點加載器,擴展點的查找,校驗,加載等核心邏輯的實現類
 |--SPI   #擴展點註解

    Dubbo的擴展點主要基於ExtensionLoader這個單例類進行加載,在進行相關擴展時需注意擴展實現的線程安全性),其具備如下特性:緩存

(1)擴展點自動包裝(Wrapper)安全

(2)擴展點自動裝配數據結構

(3)擴展點自適應(Adaptive)app

(4)擴展點自動激活(Active)框架

一、擴展點緩存

相關參數緩存在對應的數據結構中,先列出以下,方便後面看源代碼時查閱:函數

private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();

private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String,Class<?>>>();

private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();

private volatile Class<?> cachedAdaptiveClass = null;

private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();

private String cachedDefaultName;

private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();

private Set<Class<?>> cachedWrapperClasses;

(1)cachedAdaptiveClass : 當前Extension類型對應的AdaptiveExtension類型(只能一個)性能

(2)cachedWrapperClasses : 當前Extension類型對應的全部Wrapper實現類型(無順序)學習

(3)cachedActivates : 當前Extension實現自動激活實現緩存(map,無序)ui

(4)cachedNames : 擴展點實現類對應的名稱(如配置多個名稱則值爲第一個)

當loadExtensionClasses方法執行完成以後,還有一下變量被賦值:

(5)cachedDefaultName : 當前擴展點的默認實現名稱

當getExtensionClasses方法執行完成以後,除了上述變量被賦值以外,還有如下變量被賦值:

(6)cachedClasses : 擴展點實現名稱對應的實現類(一個實現類可能有多個名稱)

    其實也就是說,在調用了getExtensionClasses方法以後,當前擴展點對應的實現類的一些信息就已經加載進來了而且被緩存了。後面的許多操做均可以直接經過這些緩存數據來進行處理了。

二、ExtensionLoader實例方法

ExtensionLoader沒有提供public的構造方法,可是提供了一個public staticgetExtensionLoader,這個方法就是獲取ExtensionLoader實例的工廠方法。其public成員方法中有三個比較重要的方法:

  • getActivateExtension :根據條件獲取當前擴展可自動激活的實現
  • getExtension : 根據名稱獲取當前擴展的指定實現
  • getAdaptiveExtension : 獲取當前擴展的自適應實現

    每個ExtensionLoader實例僅負責加載特定SPI擴展的實現*。所以想要獲取某個擴展的實現,首先要獲取到該擴展對應的ExtensionLoader實例,下面咱們就來看一下獲取ExtensionLoader實例的工廠方法getExtensionLoader

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null)
        throw new IllegalArgumentException("Extension type == null");
    if(!type.isInterface()) {
        throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
    }
    if(!withExtensionAnnotation(type)) { // 只接受使用@SPI註解註釋的接口類型
        throw new IllegalArgumentException("Extension type(" + type + 
                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    }

    // 先從靜態緩存中獲取對應的ExtensionLoader實例
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); // 爲Extension類型建立ExtensionLoader實例,並放入靜態緩存
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

該方法須要一個Class類型的參數,該參數表示但願加載的擴展點類型,該參數必須是接口,且該接口必須被@SPI註解註釋,不然拒絕處理。檢查經過以後首先會檢查ExtensionLoader緩存中是否已經存在該擴展對應的ExtensionLoader,若是有則直接返回,不然建立一個新的ExtensionLoader負責加載該擴展實現,同時將其緩存起來。能夠看到對於每個擴展,dubbo中只會有一個對應的ExtensionLoader實例。

接下來看下ExtensionLoader的私有構造函數:

private ExtensionLoader(Class<?> type) {
    this.type = type;

    // 若是擴展類型是ExtensionFactory,那麼則設置爲null
    // 這裏經過getAdaptiveExtension方法獲取一個運行時自適應的擴展類型(每一個Extension只能有一個@Adaptive類型的實現,若是沒有dubbo會動態生成一個類)
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

這裏保存了對應的擴展類型,而且設置了一個額外的objectFactory屬性,他是一個ExtensionFactory類型,ExtensionFactory主要用於加載擴展的實現:

@SPI
public interface ExtensionFactory {

    /**
     * Get extension.
     * 
     * @param type object type.
     * @param name object name.
     * @return object instance.
     */
    <T> T getExtension(Class<T> type, String name);

}

同時ExtensionFactory也被@SPI註解註釋,說明他也是一個擴展點,從前面com.alibaba.dubbo.common.extension包的結構圖中能夠看到,dubbo內部提供了兩個實現類:SpiExtensionFactory 和 AdaptiveExtensionFactory,實際上還有一個SpringExtensionFactory,不一樣的實現能夠已不一樣的方式來完成擴展點實現的加載,這塊稍後再來學習。從ExtensionLoader的構造函數中能夠看到,若是要加載的擴展點類型是ExtensionFactory是,object字段被設置爲null。因爲ExtensionLoader的使用範圍有限(基本上侷限在ExtensionLoader中),所以對他作了特殊對待:在須要使用ExtensionFactory的地方,都是經過對應的自適應實現來代替。

默認的ExtensionFactory實現中,AdaptiveExtensionFactotry@Adaptive註解註釋,也就是它就是ExtensionFactory對應的自適應擴展實現(每一個擴展點最多隻能有一個自適應實現,若是全部實現中沒有被@Adaptive註釋的,那麼dubbo會動態生成一個自適應實現類),也就是說,全部對ExtensionFactory調用的地方,實際上調用的都是AdpativeExtensionFactory,那麼咱們看下他的實現代碼:

@Adaptive
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>();
        for (String name : loader.getSupportedExtensions()) { // 將全部ExtensionFactory實現保存起來
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    public <T> T getExtension(Class<T> type, String name) {
        // 依次遍歷各個ExtensionFactory實現的getExtension方法,一旦獲取到Extension即返回
        // 若是遍歷完全部的ExtensionFactory實現均沒法找到Extension,則返回null
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}

    綜上能夠看到,他會遍歷當前系統中全部的ExtensionFactory實現來獲取指定的擴展實現,獲取到擴展實現或遍歷完全部的ExtensionFactory實現。這裏調用了ExtensionLoadergetSupportedExtensions方法來獲取ExtensionFactory的全部實現,又回到了ExtensionLoader類。

三、ExtensionLoader實例方法具體流程分析

(1)、getExtension

getExtension(name)
    -> createExtension(name) #若是無緩存則建立
        -> getExtensionClasses().get(name) #獲取name對應的擴展類型
        -> 實例化擴展類
        -> injectExtension(instance) # 擴展點注入
        -> instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)) #循環遍歷全部wrapper實現,實例化wrapper並進行擴展點注入

(2)、getAdaptiveExtension

public T getAdaptiveExtension()
    -> createAdaptiveExtension() #若是無緩存則建立
        -> getAdaptiveExtensionClass().newInstance() #獲取AdaptiveExtensionClass
            -> getExtensionClasses() # 加載當前擴展全部實現,看是否有實現被標註爲@Adaptive
            -> createAdaptiveExtensionClass() #若是沒有實現被標註爲@Adaptive,則動態建立一個Adaptive實現類
                -> createAdaptiveExtensionClassCode() #動態生成實現類java代碼
                -> compiler.compile(code, classLoader) #動態編譯java代碼,加載類並實例化
        -> injectExtension(instance)

(3)、getActivateExtension 

    該方法有多個重載方法,不過最終都是調用了三個參數的那一個重載形式。其代碼結構也相對剪短,就不須要在列出概要流程了。

四、詳細代碼分析

(1)、getAdaptiveExtension 

    從前面ExtensionLoader的私有構造函數中能夠看出,在選擇ExtensionFactory的時候,並非調用getExtension(name)來獲取某個具體的實現類,而是調用getAdaptiveExtension來獲取一個自適應的實現。那麼首先咱們就來分析一下getAdaptiveExtension這個方法的實現吧:

public T getAdaptiveExtension() {
    Object instance = cachedAdaptiveInstance.get(); // 首先判斷是否已經有緩存的實例對象
    if (instance == null) {
        if(createAdaptiveInstanceError == null) {
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        instance = createAdaptiveExtension(); // 沒有緩存的實例,建立新的AdaptiveExtension實例
                        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;
}

    首先檢查緩存的adaptiveInstance是否存在,若是存在則直接使用,不然的話調用createAdaptiveExtension方法來建立新的adaptiveInstance而且緩存起來。也就是說對於某個擴展點,每次調用ExtensionLoader.getAdaptiveExtension獲取到的都是同一個實例。

private T createAdaptiveExtension() {
    try {
        return injectExtension((T) getAdaptiveExtensionClass().newInstance()); // 先獲取AdaptiveExtensionClass,在獲取其實例,最後進行注入處理
    } catch (Exception e) {
        throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
    }
}

 

createAdaptiveExtension方法中,首先經過getAdaptiveExtensionClass方法獲取到最終的自適應實現類型,而後實例化一個自適應擴展實現的實例,最後進行擴展點注入操做。先看一個getAdaptiveExtensionClass方法的實現:

private Class<?> getAdaptiveExtensionClass() {
    getExtensionClasses(); // 加載當前Extension的全部實現,若是有@Adaptive類型,則會賦值爲cachedAdaptiveClass屬性緩存起來
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass(); // 沒有找到@Adaptive類型實現,則動態建立一個AdaptiveExtensionClass
}

    他只是簡單的調用了getExtensionClasses方法,而後在判adaptiveCalss緩存是否被設置,若是被設置那麼直接返回,不然調用createAdaptiveExntesionClass方法動態生成一個自適應實現,關於動態生成自適應實現類而後編譯加載而且實例化的過程這裏暫時不分析,留到後面在分析吧。這裏咱們看getExtensionClassses方法:

private Map<String, Class<?>> getExtensionClasses() {
    Map<String, Class<?>> classes = cachedClasses.get(); // 判斷是否已經加載了當前Extension的全部實現類
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                classes = loadExtensionClasses(); // 若是尚未加載Extension的實現,則進行掃描加載,完成後賦值給cachedClasses變量
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

    在getExtensionClasses方法中,首先檢查緩存的cachedClasses,若是沒有再調用loadExtensionClasses方法來加載,加載完成以後就會進行緩存。也就是說對於每一個擴展點,其實現的加載只會執行一次。loadExtensionClasses方法在上文已經介紹過,就是讀取對應的配置文件,並將相關配置進行緩存。在此再也不贅述。

(2)、injectExtension

    建立自適應擴展點實現類型和實例化就已經完成了,下面就來看下擴展點自動注入的實現:

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())) {// 處理全部set方法
                    Class<?> pt = method.getParameterTypes()[0];// 獲取set方法參數類型
                    try {
                        // 獲取setter對應的property名稱
                        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                        Object object = objectFactory.getExtension(pt, property); // 根據類型,名稱信息從ExtensionFactory獲取
                        if (object != null) { // 若是不爲空,說set方法的參數是擴展點類型,那麼進行注入
                            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;
}

    這裏能夠看到,擴展點自動注入的一句就是根據setter方法對應的參數類型和property名稱從ExtensionFactory中查詢,若是有返回擴展點實例,那麼就進行注入操做。到這裏getAdaptiveExtension方法就分析完畢了。

(3)getExtension

這個方法的主要做用是用來獲取ExtensionLoader實例表明的擴展的指定實現。已擴展實現的名字做爲參數,結合前面學習getAdaptiveExtension的代碼,咱們能夠推測,這方法中也使用了在調用getExtensionClasses方法的時候收集並緩存的數據,其中涉及到名字和具體實現類型對應關係的緩存屬性是cachedClasses。具體是是否如咱們猜測的那樣呢,學習一下相關代碼就知道了:

public T getExtension(String name) {
    if (name == null || name.length() == 0)
        throw new IllegalArgumentException("Extension name == null");
    if ("true".equals(name)) {  // 判斷是不是獲取默認實現
        return getDefaultExtension();
    }
    Holder<Object> holder = cachedInstances.get(name);// 緩存
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<Object>());
        holder = cachedInstances.get(name);
    }
    Object instance = holder.get();
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                instance = createExtension(name);// 沒有緩存實例則建立
                holder.set(instance);// 緩存起來
            }
        }
    }
    return (T) instance;
}

    接着看createExtension方法的實現:

private T createExtension(String name) {
    Class<?> clazz = getExtensionClasses().get(name); // getExtensionClass內部使用cachedClasses緩存
    if (clazz == null) {
        throw findException(name);
    }
    try {
        T instance = (T) EXTENSION_INSTANCES.get(clazz); // 從已建立Extension實例緩存中獲取
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        injectExtension(instance); // 屬性注入

        // Wrapper類型進行包裝,層層包裹
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && wrapperClasses.size() > 0) {
            for (Class<?> wrapperClass : wrapperClasses) {
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                type + ")  could not be instantiated: " + t.getMessage(), t);
    }
}

    從代碼中能夠看到,內部調用了getExtensionClasses方法來獲取當前擴展的全部實現,而getExtensionClassse方法會在第一次被調用的時候將結果緩存到cachedClasses變量中,後面的調用就直接從緩存變量中獲取了。這裏還能夠看到一個緩存EXTENSION_INSTANCES,這個緩存是ExtensionLoader的靜態成員,也就是全局緩存,存放着全部的擴展點實現類型與其對應的已經實例化的實例對象(是全部擴展點,不是某一個擴展點),也就是說全部的擴展點實如今dubbo中最多都只會有一個實例。

拿到擴展點實現類型對應的實例以後,調用了injectExtension方法對該實例進行擴展點注入,緊接着就是遍歷該擴展點接口的全部Wrapper來對真正的擴展點實例進行Wrap操做,都是對經過將上一次的結果做爲下一個Wrapper的構造函數參數傳遞進去實例化一個Wrapper對象,最後總返回回去的是Wrapper類型的實例而不是具體實現類的實例。

這裏或許有一個疑問: 從代碼中看,不論instance是否存在於EXTENSION_INSTANCE,都會進行擴展點注入和Wrap操做。那麼若是對於同一個擴展點,調用了兩次createExtension方法的話,那不就進行了兩次Wrap操做麼?

若是外部可以直接調用createExtension方法,那麼確實可能出現這個問題。可是因爲createExtension方法是private的,所以外部沒法直接調用。而在ExtensionLoader類中調用它的getExtension方法(只有它這一處調用),內部本身作了緩存(cachedInstances),所以當getExtension方法內部調用了一次createExtension方法以後,後面對getExtension方法執行一樣的調用時,會直接使用cachedInstances緩存而不會再去調用createExtension方法了。

(4)、getActivateExtension

getActivateExtension方法主要獲取當前擴展的全部可自動激活的實現。可根據入參(values)調整指定實現的順序,在這個方法裏面也使用到getExtensionClasses方法中收集的緩存數據。

public List<T> getActivateExtension(URL url, String[] values, String group) {
    List<T> exts = new ArrayList<T>();
    List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values); // 解析配置要使用的名稱

    // 若是未配置"-default",則加載全部Activates擴展(names指定的擴展)
    if (! names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
        getExtensionClasses(); // 加載當前Extension全部實現,會獲取到當前Extension中全部@Active實現,賦值給cachedActivates變量
        for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) { // 遍歷當前擴展全部的@Activate擴展
            String name = entry.getKey();
            Activate activate = entry.getValue();
            if (isMatchGroup(group, activate.group())) { // 判斷group是否知足,group爲null則直接返回true
                T ext = getExtension(name); // 獲取擴展現例

                // 排除names指定的擴展;而且若是names中沒有指定移除該擴展(-name),且當前url匹配結果顯示可激活才進行使用
                if (! names.contains(name)
                        && ! names.contains(Constants.REMOVE_VALUE_PREFIX + name) 
                        && isActive(activate, url)) {
                    exts.add(ext);
                }
            }
        }
        Collections.sort(exts, ActivateComparator.COMPARATOR); // 默認排序
    }

    // 對names指定的擴展進行專門的處理
    List<T> usrs = new ArrayList<T>();
    for (int i = 0; i < names.size(); i ++) { // 遍歷names指定的擴展名
        String name = names.get(i);
        if (! name.startsWith(Constants.REMOVE_VALUE_PREFIX)
                && ! names.contains(Constants.REMOVE_VALUE_PREFIX + name)) { // 未設置移除該擴展
            if (Constants.DEFAULT_KEY.equals(name)) { // default表示上面已經加載而且排序的exts,將排在default以前的Activate擴展放置到default組以前,例如:ext1,default,ext2
                if (usrs.size() > 0) { // 若是此時user不爲空,則user中存放的是配置在default以前的Activate擴展
                    exts.addAll(0, usrs); // 注意index是0,放在default前面
                    usrs.clear(); // 放到default以前,而後清空
                }
            } else {
                T ext = getExtension(name);
                usrs.add(ext);
            }
        }
    }
    if (usrs.size() > 0) { // 這裏留下的都是配置在default以後的
        exts.addAll(usrs); // 添加到default排序以後
    }
    return exts;
}

2、AdaptiveExtension動態生成及@Adaptive
    

    Adaptive:由於dubbo底層會大量使用反射,出於性能考慮默認使用javassist字節碼編譯生成一個adaptive,由它動態委派處理。用戶能夠本身實現一個adaptive,只須要對某個類打上@adaptive便可。對於默認編譯生成Adaptive的方案,須要使用@Adaptive聲明接口上的哪些方法是adaptive方法。擴展點名稱的key默認是接口類型上@SPI#value,方法上的@Adaptive#value有更高優先級。

官方文檔描述動態生成的AdaptiveExtension代碼以下:

package <擴展點接口所在包>;

public class <擴展點接口名>$Adpative implements <擴展點接口> {
    public <有@Adaptive註解的接口方法>(<方法參數>) {
        if(是否有URL類型方法參數?) 使用該URL參數
        else if(是否有方法類型上有URL屬性) 使用該URL屬性
        # <else 在加載擴展點生成自適應擴展點類時拋異常,即加載擴展點失敗!>

        if(獲取的URL == null) {
            throw new IllegalArgumentException("url == null");
        }

        根據@Adaptive註解上聲明的Key的順序,從URL獲致Value,做爲實際擴展點名。
        如URL沒有Value,則使用缺省擴展點實現。如沒有擴展點, throw new IllegalStateException("Fail to get extension");

        在擴展點實現調用該方法,並返回結果。
    }

    public <有@Adaptive註解的接口方法>(<方法參數>) {
        throw new UnsupportedOperationException("is not adaptive method!");
    }
}

規則以下:

  • 先在URL上找@Adaptive註解指定的Extension名;
  • 若是不設置則缺省使用Extension接口類名的點分隔小寫字串(即對於Extension接口com.alibaba.dubbo.xxx.YyyInvokerWrapper的缺省值爲String[] {「yyy.invoker.wrapper」})。
  • 使用默認實現(@SPI指定),若是沒有設定缺省擴展,則方法調用會拋出IllegalStateException。

    動態注入的代碼以下:

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

     

從上面能夠看到,進行注入的條件以下:

  1. set開頭的方法
  2. 方法的參數只有一個
  3. 方法必須是public

咱們知道一個接口的實現者可能有多個,此時到底注入哪個呢?

@Adaptive 的 dubbo spi 擴展機制,它獲取設配類不在經過前面過程生成設配類 java 源代碼, 而是在讀取擴展文件的時候遇到實現類打了註解@Adaptive 就把這個類做爲設配類緩存在 ExtensionLoader 中,調用是直接返回。使用時首先經過ExtensionLoader生成了XXX(例如Protocol)的Adaptive,以在運行期經過動態決策委託實體對象處理。此時採起的策略是,並不去注入一個具體的實現者,而是注入一個動態生成的實現者,這個動態生成的實現者的邏輯是肯定的,可以根據不一樣的參數來使用不一樣的實現者實現相應的方法。這個動態生成的實現者的class就是ExtensionLoader的Class<?> cachedAdaptiveClass

方法的參數必須是接口,而且是ExtensionLoader可以獲取其擴展類。

繼續看實現:

@SuppressWarnings("unchecked")
private T createAdaptiveExtension() {
    try {
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
    }
}

 

會調用:

private Class<?> getAdaptiveExtensionClass() {
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

繼續調用:

private Class<?> createAdaptiveExtensionClass() {
    String code = createAdaptiveExtensionClassCode();
    ClassLoader classLoader = findClassLoader();
    com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

繼續:

private String createAdaptiveExtensionClassCode() {
    StringBuilder codeBuidler = new StringBuilder();
    Method[] methods = type.getMethods();
    boolean hasAdaptiveAnnotation = false;
    for(Method m : methods) {
        if(m.isAnnotationPresent(Adaptive.class)) {
            hasAdaptiveAnnotation = true;
            break;
        }
    }
    // 徹底沒有Adaptive方法,則不須要生成Adaptive類
    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() + "$Adpative" + " implements " + type.getCanonicalName() + " {");
    
    for (Method method : methods) {
        Class<?> rt = method.getReturnType();
        Class<?>[] pts = method.getParameterTypes();
        Class<?>[] ets = method.getExceptionTypes();

        Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
        StringBuilder code = new StringBuilder(512);
        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;
            for (int i = 0; i < pts.length; ++i) {
                if (pts[i].equals(URL.class)) {
                    urlTypeIndex = i;
                    break;
                }
            }
            // 有類型爲URL的參數
            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);
            }
            // 參數沒有URL類型
            else {
                String attribMethod = null;
                
                // 找到參數的URL屬性
                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 adative 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);
            }
            
            String[] value = adaptiveAnnotation.value();
            // 沒有設置Key,則使用「擴展點接口名的點分隔 做爲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;
            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;
                }
            }
            
            String defaultExtName = cachedDefaultName;
            String getNameCode = null;
            for (int i = value.length - 1; i >= 0; --i) {
                if(i == value.length - 1) {
                    if(null != defaultExtName) {
                        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], defaultExtName);
                        else
                            getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
                    }
                    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\")", 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();
}

以Protocol爲例,動態生成一個類:

package com.jason.dubbo;

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

public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {

    public void destroy() {
        throw new UnsupportedOperationException(
                                                "method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException(
                                                "method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1)
                                                                                                       throws com.alibaba.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null) throw new IllegalStateException(
                                                             "Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url("
                                                                     + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }

    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0)
                                                                                    throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException(
                                                                      "com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null) throw new IllegalStateException(
                                                             "Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url("
                                                                     + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
}

從上面的代碼中能夠看到,Protocol$Adpative是根據URL參數中protocol屬性的值來選擇具體的實現類的。

如值爲dubbo,則從ExtensionLoader<Protocol>中獲取dubbo對應的實例,即DubboProtocol實例

如值爲hessian,則從ExtensionLoader<Protocol>中獲取hessian對應的實例,即HessianProtocol實例

也就是說Protocol$Adpative可以根據url中的protocol屬性值動態的採用對應的實現。

對於上述獲取動態實現者即Protocol$Adpative的過程還須要補充一些細節內容:

1 要求對應的接口中的某些方法必須含有Adaptive註解,沒有Adaptive註解,則表示不須要生成動態類

2 對於接口的方法中不含Adaptive註解的,所有是不可調用的,如上述的destroy()方法

3 含有Adaptive註解的方法必須含有URL類型的參數,或者可以獲取到URL,分別如上述的refer方法和export方法

4 從URL中根據什麼參數來獲取實現者信息呢?以Protocol爲例,參數就爲"protocol",默認是接口簡單名稱首字母小寫或者接口中指定的默認實現,

 

3、Wrapper

    包裝類必須有一個參數爲spi接口類型的構造函數,不然不能正常工做。判斷warpper的標準是class有沒有一個參數爲接口類型的構造參數。Wrapper能夠有多個,會被按順序依次覆蓋,假設spi定義以下:

A=a.b.c

B=a.b.wrapper1

C=a.b.wrapper2

wrapper的最終結構則爲B-C-A

 

總結

基本上將dubbo的擴展點加載機制學習了一遍,有幾點可能須要注意的地方:

  • 每一個ExtensionLoader實例只負責加載一個特定擴展點實現
  • 每一個擴展點對應最多隻有一個ExtensionLoader實例
  • 對於每一個擴展點實現,最多隻會有一個實例
  • 一個擴展點實現能夠對應多個名稱(逗號分隔)
  • 對於須要等到運行時才能決定使用哪個具體實現的擴展點,應獲取其自使用擴展點實現(AdaptiveExtension)
  • @Adaptive註解要麼註釋在擴展點@SPI的方法上,要麼註釋在其實現類的類定義上
  • 若是@Adaptive註解註釋在@SPI接口的方法上,那麼原則上該接口全部方法都應該加@Adaptive註解(自動生成的實現中默認爲註解的方法拋異常)
  • 每一個擴展點最多隻能有一個被AdaptiveExtension
  • 每一個擴展點能夠有多個可自動激活的擴展點實現(使用@Activate註解)
  • 因爲每一個擴展點實現最多隻有一個實例,所以擴展點實現應保證線程安全
  • 若是擴展點有多個Wrapper,那麼最終其執行的順序不肯定(內部使用ConcurrentHashSet存儲)

    •  

    參考文獻:

    一、參考:http://blog.csdn.net/jdluojing/article/details/44947221

    http://www.tuicool.com/articles/FR7NnyQ Dubbo源碼學習之ExtentionLoader

    http://blog.csdn.net/jdluojing/article/details/44947221

相關文章
相關標籤/搜索