【Dubbo筆記】dubbo SPI、Adaptive機制解讀

    在很多場合,系統需要根據上下文、參數決定邏輯。例如支付,使用微信、支付寶、銀聯支付邏輯不一樣,比較直接的寫法是用 【if】判斷,走不同的分支;另外,設計模式裏面有一種工廠模式也適用這種場景。【Dubbo】裏也有很多這種場景(多協議等等),但是【Dubbo】使用SPI(Service Provider Interface) + Adaptive機制來應付這種場景。這種機制更靈活、更具擴展性。

1 Dubbo SPI

    區別於JDK原生的SPI,【Dubbo】自己實現了一套SPI機制實時加載具體的實現類。org.apache.dubbo.common.extension.ExtensionLoader類是Dubbo SPI機制的核心,看一下【ExtensionLoader】的幾個核心方法。

public class ExtensionLoader<T> {
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);

    private final Class<?> type;
    private final ExtensionFactory objectFactory;
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
    private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
    private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
    private volatile Class<?> cachedAdaptiveClass = null;
    private String cachedDefaultName;
    private Set<Class<?>> cachedWrapperClasses;

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

    @SuppressWarnings("unchecked")
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {}

    /**
     * Get activate extensions.
     *
     * @param url    url
     * @param values extension point names
     * @param group  group
     * @return extension list which are activated
     * @see org.apache.dubbo.common.extension.Activate
     */
    public List<T> getActivateExtension(URL url, String[] values, String group) {}

    /**
     * Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException}
     * will be thrown.
     */
    @SuppressWarnings("unchecked")
    public T getExtension(String name) {}

    @SuppressWarnings("unchecked")
    public T getAdaptiveExtension(){}
}

【ExtensionLoader】使用了單例模式,既有靜態成員、又有實例成員。

  1.1 【ExtensionLoader】靜態成員

  • EXTENSION_LOADERS 靜態變量 ConcurrentHashMap 用來存儲各SPI接口對應【ExtensionLoader】實例,每個SPI接口的ExtensionLoader都是一個單例,key是SPI接口類;
  • EXTENSION_INSTANCES 靜態變量 ConcurrentHashMap 用來存儲所有已加載的實現類的實例, key是實現類;
  • <T> ExtensionLoader<T> getExtensionLoader(Class<T> type)  靜態方法 獲取SPI接口(type)的ExtensionLoader實例;
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
            if (type == null) {
                throw new IllegalArgumentException("Extension type == null");
            }
            //筆者注  不是接口類型 不允許創建ExtensionLoader
            if (!type.isInterface()) {
                throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
            }
            //筆者注  沒有@SPI註解 也不允許創建ExtensionLoader
            if (!withExtensionAnnotation(type)) {
                throw new IllegalArgumentException("Extension type (" + type +
                        ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
            }
            //筆者注  如果有創建過則直接使用,沒有則創建一個ExtensionLoader實例
            ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
            if (loader == null) {
                EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
                loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
            }
            return loader;
        }
     

  1.2 【ExtensionLoader】實例成員

  • type  實例變量 當前【ExtensionLoader】實例關聯的SPI接口;

  • objectFactory 實例變量 ExtensionFactory 用來給SPI接口實例自動注入屬性,類似於Spring框架的IOC;
  • cachedNames 實例變量 ConcurrentMap 緩存實現類對應的擴展名 
  • cachedClasses 實例變量 用來緩存擴展名對應的實現類
  • cachedActivates 實例變量 ConcurrentHashMap  如果實現類有@Activate註解,則將註解緩存下來
  • cachedInstances 實例變量 ConcurrentHashMap 用來緩存擴展名對應的實現類實例
  • cachedAdaptiveInstance 實例變量 用來緩存SPI接口的Adaptive類的實例
  • cachedAdaptiveClass 實例變量  用來緩存SPI接口的Adaptive類
  • cachedDefaultName 實例變量 用來緩存SPI接口的默認實現類的擴展名,註解@SPI的value
  • cachedWrapperClasses 實例變量 緩存SPI接口的包裝類
  • T getExtension(String name) 實例方法 獲取擴展名[name]對應的實現類實例  

    從磁盤中(META-INF/dubbo/external、META-INF/dubbo/internal、META-INF/dubbo、META-INF/services)加載對應SPI接口的實現類,找到指定擴展名[name]的類,創建實例並自動注入相關屬性。

    自動注入只對有且僅有一個參數且參數類型不是基本類型的setter方法,AdaptiveExtensionFactory.getExtension,通過實現類SpringExtensionFactory跟Spring容器結合起來注入Spring的Bean。

public class ExtensionLoader<T> {
    
    //筆者注:如果已經加載過則使用,否則加載實現類並返回name對應的實例
    public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }

        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {

                    //筆者注 當前是loader中沒有緩存,則去創建
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

    //筆者注 創建name對應擴展實例
    private T createExtension(String name) {
        //筆者注 獲取name對應實現類
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            //筆者注 全局的實例緩存中如果有對應實現類的實例 則直接使用,否則創建一個實例
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //筆者注 自動注入屬性
            injectExtension(instance);
            //筆者注 如果有包裝類 則包裝對應的實例
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
             
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

    // 筆者注: 對實例自動注入屬性,只對只有一個參數且參數類型不是基本類型(Integer、Double等)的setter方法注入。
    //注入的對象是通過ExtensionFactory的Adaptive類AdaptiveExtensionFactory獲取,
    //AdaptiveExtensionFactory跟Spring的容器結合起來了
    private T injectExtension(T instance) {

        if (objectFactory == null) {
            return instance;
        }

        try {
            for (Method method : instance.getClass().getMethods()) {
                if (!isSetter(method)) {
                    continue;
                }
                /**
                 * Check {@link DisableInject} to see if we need auto injection for this property
                 */
                if (method.getAnnotation(DisableInject.class) != null) {
                    continue;
                }
                Class<?> pt = method.getParameterTypes()[0];
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }

                try {
                    String property = getSetterProperty(method);
                    Object object = objectFactory.getExtension(pt, property);
                    if (object != null) {
                        method.invoke(instance, object);
                    }
                } catch (Exception e) {
                    logger.error("Failed to inject via method " + method.getName()
                            + " of interface " + type.getName() + ": " + e.getMessage(), e);
                }

            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }
    
    //筆者注 取SPI接口的實現類,首先從緩存取,取不到則從磁盤文件里加載實現類
    //(META-INF/dubbo/external、META-INF/dubbo/internal、META-INF/dubbo、META-INF/services)
    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;
    }
}

 

  • T getAdaptiveExtension() 實例方法  獲取SPI接口對應的Adaptive類的實例。

     Dubbo有兩種方法創建Adaptive類,其一是在某個實現類上添加註解@Adaptive則表明這個方法就是該SPI接口的Adaptive類,其二是Dubbo自動生成一個Adaptive類文件並編譯成class對象。

    自動生成的Adaptive類只對SPI接口中有@Adaptive註解的方法做處理,沒有@Adaptive註解的方法生成的方法體直接是"throw new UnsupportedOperationException"。有註解的方法生成的方法體實現的邏輯是:

  1.  找URL對象:類型URL的參數,沒有則在其它參數中尋找"返回類型爲URL"的方法並獲得URL對象,沒有則報錯。
  2. 在URL中依次找keys集合中的key對應的value作爲實際擴展名,都沒找到則使用SPI接口默認擴展名;keys集合來源於方法上@Adaptive註解的value數組;如果數組爲空,則用SPI接口的類名(駝峯結構轉爲xx.yy.zz形式);實際的代碼形如String extName = url.getParameter("key1", url.getParameter("key2", url.getParameter("key3", "a")))。
  3. 利用【ExtensionLoader】獲取擴展名對應的實現類實例。
  4. 調用實際實例的方法,返回結果。
public class ExtensionLoader<T> {

    //筆者注 獲取SPI接口對應的Adaptive類的實例,先從緩存中取,沒有則自動生成一個Adaptive類創建實例
    public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError != null) {
                throw new IllegalStateException("Failed to create adaptive instance: " +
                        createAdaptiveInstanceError.toString(),
                        createAdaptiveInstanceError);
            }

            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        instance = createAdaptiveExtension();
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        createAdaptiveInstanceError = t;
                        throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                    }
                }
            }
        }

        return (T) instance;
    }

    @SuppressWarnings("unchecked")
    private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }
   
   
   private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        
        //筆者注 沒有則自動生成一個Adaptive類
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

   private Class<?> createAdaptiveExtensionClass() {
        //筆者注 自動生成一個Adaptive類
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        ClassLoader classLoader = findClassLoader();
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }
}

public class AdaptiveClassCodeGenerator {
    private static final String CODE_CLASS_DECLARATION = "public class %s$Adaptive implements %s {\n";

    private static final String CODE_METHOD_DECLARATION = "public %s %s(%s) %s {\n%s}\n";

    private static final String CODE_METHOD_ARGUMENT = "%s arg%d";

    private static final String CODE_METHOD_THROWS = "throws %s";

    private static final String CODE_UNSUPPORTED = "throw new UnsupportedOperationException(\"The method %s of interface %s is not adaptive method!\");\n";

    private static final String CODE_URL_NULL_CHECK = "if (arg%d == null) throw new IllegalArgumentException(\"url == null\");\n%s url = arg%d;\n";

    private static final String CODE_EXT_NAME_ASSIGNMENT = "String extName = %s;\n";
    
    //筆者注 生成Adaptive類,拼接類的字符串,然後編譯加載
    public String generate() {
        // no need to generate adaptive class since there's no adaptive method found.
        if (!hasAdaptiveMethod()) {
            throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
        }

        StringBuilder code = new StringBuilder();
        code.append(generatePackageInfo());
        code.append(generateImports());
        code.append(generateClassDeclaration());

        Method[] methods = type.getMethods();
        for (Method method : methods) {
            code.append(generateMethod(method));
        }
        code.append("}");

        if (logger.isDebugEnabled()) {
            logger.debug(code.toString());
        }
        return code.toString();
    }
}

    

  • List<T> getActivateExtension(URL url, String[] values, String group) 實例方法 獲取激活的實現類實例,values是擴展名列表("-name"表示剔除該擴展名對應的實現類)、group一般就是provider(提供方)、consumer(消費方)

    邏輯如下 

  1.  如果不剔除默認(即values[]沒有"-default")則先收集[默認激活實例];  
  2. 依次獲取values中實現類的實例("-name"表示剔除name對應的實例,即不獲取);
  3. 根據"default"在values位置確定[默認激活實例]的位置,如果沒有"default"則表示[默認激活實例]在最前面,values[]裏面對應的實例依次放在後面;
public class ExtensionLoader<T> {

    public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> activateExtensions = new ArrayList<>();
        List<String> names = values == null ? new ArrayList<>(0) : asList(values);
        
        //筆者注 如果不剔除默認(即沒有"-default")則先收集[默認激活實例]
        //[默認激活實例]有幾個特點
        //(1)實現類上有@Activate註解且跟group和url匹配 
        //(2)實現類擴展名不在values中("-name"或name)
        if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
            getExtensionClasses();
            for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                Object activate = entry.getValue();

                String[] activateGroup, activateValue;

                if (activate instanceof Activate) {
                    activateGroup = ((Activate) activate).group();
                    activateValue = ((Activate) activate).value();
                } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
                    activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
                    activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
                } else {
                    continue;
                }
                if (isMatchGroup(group, activateGroup)
                        && !names.contains(name)
                        && !names.contains(REMOVE_VALUE_PREFIX + name)
                        && isActive(activateValue, url)) {
                    activateExtensions.add(getExtension(name));
                }
            }
            activateExtensions.sort(ActivateComparator.COMPARATOR);
        }

        //筆者注 依次獲取values中實現類的實例(-name表示剔除,不獲取)。根據"default"位置決定默認[激活實例]位置
        List<T> loadedExtensions = new ArrayList<>();
        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i);
            if (!name.startsWith(REMOVE_VALUE_PREFIX)
                    && !names.contains(REMOVE_VALUE_PREFIX + name)) {

                //筆者注 如果是"default"說明之前的實現類實例,要放在[默認激活實例]之前
                if (DEFAULT_KEY.equals(name)) {
                    if (!loadedExtensions.isEmpty()) {
                        activateExtensions.addAll(0, loadedExtensions);
                        loadedExtensions.clear();
                    }
                } else {
                    loadedExtensions.add(getExtension(name));
                }
            }
        }
        //其餘的 放在[默認激活實例]之後
        if (!loadedExtensions.isEmpty()) {
            activateExtensions.addAll(loadedExtensions);
        }
        return activateExtensions;
    }
}

 

   

2 SPI在dubbo中應用

    【Dubbo】廣泛使用SPI機制,所有@SPI註解的接口都是一個SPI擴展點,如下圖:

3 示例

    1 首先定義一個@SPI註解的接口,默認實現類是"a"

package com.focuse.jdkdemo.dubbospi;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;

@SPI("a")
public interface DubboSpiTest {
    @Adaptive
    String read(URL url);

    void write(String a);
}

    2 定義2個實現類DubboSpiTestImplA、DubboSpiTestImplB

package com.focuse.jdkdemo.dubbospi;

import org.apache.dubbo.common.URL;

public class DubboSpiTestImplA implements DubboSpiTest{

    public String read(URL url) {
        return "Hello A!!";
    }

    public void write(String a){

    }
}
package com.focuse.jdkdemo.dubbospi;

import org.apache.dubbo.common.URL;

public class DubboSpiTestImplB implements DubboSpiTest{

    public String read(URL url) {
        return "Hello B!!";
    }

    public void write(String a){

    }
}

3 創建spi文件 META-INF/dubbo/com.focuse.jdkdemo.dubbospi.DubboSpiTest

a=com.focuse.jdkdemo.dubbospi.DubboSpiTestImplA
b=com.focuse.jdkdemo.dubbospi.DubboSpiTestImplB

4 ExtensinoLoader獲取DubboSpiTest的Adaptive類的實例

package com.focuse.jdkdemo.dubbospi;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;

import java.util.HashMap;

/**
 * @author :
 * @date :Created in 2020/6/6 下午3:08
 * @description:
 * @modified By:
 */
public class DubboSpiApplication {


    public static void main(String[] args) throws Exception{
        //獲取DubboSpiTest接口的adaptive類
        DubboSpiTest dubboSpiTest = ExtensionLoader.getExtensionLoader(DubboSpiTest.class).getAdaptiveExtension();
        HashMap<String, String> params = new HashMap<>();
        //上下文中指定實現類,因爲DubboSpiTest.read方法上註解@Adaptive沒有value值,
        //所以上下文的key就是DubboSpiTest類名(駝峯轉xxx.yyy.zz形式)
        params.put("dubbo.spi.test", "b");
        URL context = new URL("dubbo", "DubboSpiTest", 0, params);
        String result = dubboSpiTest.read(context);
        System.out.println("*************************");
        System.out.println(result);
        System.out.println("*************************");
    }
}

在上下文中指定類實現類用"b"(因爲DubboSpiTest.read方法上註解@Adaptive沒有value值,所以上下文的key就是DubboSpiTest類名(駝峯轉xxx.yyy.zz形式)),所以實際調用的是DubboSpiTestImplB.read()

如果不指定,則會使用默認的"a"

DubboSpiTest$Adaptive是自動生成的DubboSpiTest的Adaptive類,擴展名先從上下文中取,沒有則用默認的"a",拿到擴展名後再獲取擴展名對應的實例。

package com.focuse.jdkdemo.dubbospi;

import org.apache.dubbo.common.extension.ExtensionLoader;

public class DubboSpiTest$Adaptive implements com.focuse.jdkdemo.dubbospi.DubboSpiTest {
    public void write(java.lang.String arg0) {
        throw new UnsupportedOperationException("The method public abstract void com.focuse.jdkdemo.dubbospi.DubboSpiTest.write(java.lang.String) of interface com.focuse.jdkdemo.dubbospi.DubboSpiTest is not adaptive method!");
    }

    public java.lang.String read(org.apache.dubbo.common.URL arg0) {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg0;
        String extName = url.getParameter("dubbo.spi.test", "a");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (com.focuse.jdkdemo.dubbospi.DubboSpiTest) name from url (" + url.toString() + ") use keys([dubbo.spi.test])");
        com.focuse.jdkdemo.dubbospi.DubboSpiTest extension = (com.focuse.jdkdemo.dubbospi.DubboSpiTest) ExtensionLoader.getExtensionLoader(com.focuse.jdkdemo.dubbospi.DubboSpiTest.class).getExtension(extName);
        return extension.read(arg0);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章