Dubbo-Activate實現原理

2022-11-20 21:01:38

前言

在Dubbo中有Filter使用,對於Filter來說我們會遇到這樣的問題,Filter自身有很多的實現,我們希望某種條件下使用A實現,另外情況下使用B實現,這個時候我們前面介紹@SPI和@Adaptive就不能滿足我們要求了,這個時候我們就需要使用@Activate。 Activate註解表示一個擴充套件是否被啟用(使用),可以放在類定義和方法上,Dubbo中用它在擴充套件類定義上,表示這個擴充套件實現啟用條件和時機。

如何使用

  1. 自定義介面;
@SPI
public interface ActivateDemo {

    /**
     * 測試
     * @param msg
     * @return
     */

    String test(String msg);

}
  1. 實現介面,分別進行預設實現、多個組、排序、從URL獲取值,多種方式的案例;
@Activate(group = {"default"})
public class DefaultActivateDemoImpl implements ActivateDemo {
    @Override
    public String test(String msg) {
        return msg;
    }
}

@Activate(group = {"groupA","groupB"})
public class ComposeGroupActivateDemoImpl implements ActivateDemo {

    @Override
    public String test(String msg) {
        return msg;
    }
}

@Activate(order = 1, group = {"order"})
public class Order1ActivateDemoImpl implements ActivateDemo{
    @Override
    public String test(String msg) {
        return msg;
    }
}

@Activate(order = 2, group = {"order"})
public class Order2ActivateDemoImpl implements ActivateDemo{
    @Override
    public String test(String msg) {
        return msg;
    }
}

@Activate(value = {"value"}, group = {"group"})
public class ValueAndGroupActivateDemoImpl implements ActivateDemo{
    @Override
    public String test(String msg) {
        return msg;
    }
}
  1. 在resources下新建META-INF/dubbo/internal資料夾,新建自己定義介面的全限定名檔名,名稱以及內容可參考以下內容;
image.png
image.png
  1. 測試案例;
    public static void main(String[] args) {  
        
        ExtensionLoader<ActivateDemo> loader = ExtensionLoader.getExtensionLoader(ActivateDemo.class);
        URL url = URL.valueOf("test://localhost/test");
        List<ActivateDemo> list = loader.getActivateExtension(url, new String[]{}, "order");
        System.out.println(list.size());
        list.forEach(item -> System.out.println(item.getClass()));
    
    }
    public static void main(String[] args) {
        ExtensionLoader<ActivateDemo> loader = ExtensionLoader.getExtensionLoader(ActivateDemo.class);
        URL url = URL.valueOf("test://localhost/test");
        //注意這裡要使用url接收,不能直接url.addParameter()
        url = url.addParameter("value""test");
        List<ActivateDemo> list = loader.getActivateExtension(url, new String[]{"order1""default"}, "group");
        System.out.println(list.size());
        list.forEach(item -> System.out.println(item.getClass()));
    }

原始碼分析

@Activate註解標註在擴充套件實現類上,有 group、value 以及 order 三個屬性,三個屬性作用如下:

  1. group 修飾的實現類可以列舉為一種標籤,標籤用來區分是在 Provider 端被啟用還是在 Consumer 端被啟用;
  2. value 修飾的實現類只在 URL 引數中出現指定的 key 時才會被啟用;
  3. order 用來確定擴充套件實現類的排序;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
   
    String[] group() default {};

    String[] value() default {};
    
    @Deprecated
    String[] before() default {};

    @Deprecated
    String[] after() default {};

    int order() default 0;
}

SPI在擴充套件類載入時候, loadClass() 方法會對 @Activate的註解類進行掃描,其中會將包含 @Activate 註解的實現類快取到 cachedActivates 一個Map集合中,Key為擴充套件名,Value為@Activate註解;

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
                           boolean overridden)
 throws NoSuchMethodException 
{
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }
        //判斷類是否載入Adaptive註解
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz, overridden);
            //是否是擴充套件類,是的話就加入 cachedWrapperClasses 屬性
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            //檢測是否有預設構造起
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                //未設定擴充套件名,自動生成,主要用於相容java SPI的設定。
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException(
                            "No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }
            // 獲得擴充套件名,可以是陣列,有多個拓擴充套件名。
            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                //如果是自動啟用的實現類,則加入到快取
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    //儲存Class到名字的對映關係
                    cacheName(clazz, n);
                    //儲存名字到Class的對映關係
                    saveInExtensionClass(extensionClasses, clazz, n, overridden);
                }
            }
        }
    }

使用cachedActivates這個集合的地方是 getActivateExtension() ,關於此方法有4個過載函數,核心方法包含三個重要引數,URL中包含了設定資訊,Values是設定中指定的擴充套件名,Group標籤,下面是getActivateExtension的核心邏輯,首先就是獲取預設的擴充套件集合,其次將擴獲取到擴充套件類放到一個有序的集合中,按照順序新增自定義擴充套件類的實現。

    public List<T> getActivateExtension(URL url, String key) {
        return getActivateExtension(url, key, null);
    }


    public List<T> getActivateExtension(URL url, String[] values) {
        return getActivateExtension(url, values, null);
    }

    public List<T> getActivateExtension(URL url, String key, String group) {
        String value = url.getParameter(key);
        return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
    }

    public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> activateExtensions = new ArrayList<>();
        // solve the bug of using @SPI's wrapper method to report a null pointer exception.
        // TreeMap進行排序
        TreeMap<Class, T> activateExtensionsMap = new TreeMap<>(ActivateComparator.COMPARATOR);
        Set<String> loadedNames = new HashSet<>();
        //傳入的陣列包裝成為set
        Set<String> names = CollectionUtils.ofSet(values);
        //包裝好的資料中判斷不含"-default""
        if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
            //獲取所有的載入型別
            getExtensionClasses();
            //cachedActivate 儲存被@Activate修飾型別
            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;
                }
                //判斷group是否匹配
                if (isMatchGroup(group, activateGroup)
                        //沒有出現在values設定中的,即為預設啟用的擴充套件實現
                        && !names.contains(name)
                        //通過"-"明確指定不啟用該擴充套件實現
                        && !names.contains(REMOVE_VALUE_PREFIX + name)
                        //檢測URL中是否出現了指定的Key 
                        && isActive(activateValue, url)
                        //去重判斷
                        && !loadedNames.contains(name)) {
                    //篩入treeMap中
                    activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
                    loadedNames.add(name);
                }
            }
            if (!activateExtensionsMap.isEmpty()) {
                activateExtensions.addAll(activateExtensionsMap.values());
            }
        }
        List<T> loadedExtensions = new ArrayList<>();
        for (String name : names) {
            //排除對應擴充套件名 不包含以-開始 以及 一+name
            if (!name.startsWith(REMOVE_VALUE_PREFIX)
                    && !names.contains(REMOVE_VALUE_PREFIX + name)) {
                if (!loadedNames.contains(name)) {
                    if (DEFAULT_KEY.equals(name)) {
                        if (!loadedExtensions.isEmpty()) {
                            activateExtensions.addAll(0, loadedExtensions);
                            loadedExtensions.clear();
                        }
                    } else {
                        //獲取對應名字擴充套件
                        loadedExtensions.add(getExtension(name));
                    }
                    loadedNames.add(name);
                } else {
                    // If getExtension(name) exists, getExtensionClass(name) must exist, so there is no null pointer processing here.
                    String simpleName = getExtensionClass(name).getSimpleName();
                    logger.warn("Catch duplicated filter, ExtensionLoader will ignore one of them. Please check. Filter Name: " + name +
                            ". Ignored Class Name: " + simpleName);
                }
            }
        }
        if (!loadedExtensions.isEmpty()) {
            activateExtensions.addAll(loadedExtensions);
        }
        return activateExtensions;
    }

結束

歡迎大家點點關注,點點贊!