Spring Dubbo開發筆記(三)——dubbo擴展

完整的代碼及:java

github: https://github.com/Athlizo/spring-dubbo-parentgit

碼雲: https://git.oschina.net/null_584_3382/spring-dubbo-parentgithub

1.概述:

dubbo框架中,提供了多種擴展,好比Dubbo的過濾器擴展,路由擴展等等。而且dubbo已經提供了擴展的一些默認實現。本篇文章主要介紹:1)dubbo擴展原理,2)經過簡單的改造,使dubbo讓擴展的使用更方便。spring

2. dubbo擴展

2.1 怎麼建立dubbo擴展

以攔截器做爲例子說明,引用dubbo官方文檔中的一個圖。app

<!-- 在xml配置文件中設置 -->
<dubbo:reference filter="xxx,yyy" /> <!-- 消費方調用過程攔截 -->
<dubbo:consumer filter="xxx,yyy"/> <!-- 消費方調用過程缺省攔截器,將攔截全部reference -->
<dubbo:service filter="xxx,yyy" /> <!-- 提供方調用過程攔截 -->
<dubbo:provider filter="xxx,yyy"/> <!-- 提供方調用過程缺省攔截器,將攔截全部service -->
dubbo擴展配置文件
src
 |-main
    |-java
        |-com
            |-xxx
                |-XxxFilter.java (實現Filter接口)
    |-resources
        |-META-INF
            |-dubbo
                |-com.alibaba.dubbo.rpc.Filter (純文本文件,內容爲:xxx=com.xxx.XxxFilter)
//擴展類
package com.xxx;
 
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcException;
 
 
public class XxxFilter implements Filter {
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // before filter ...
        Result result = invoker.invoke(invocation);
        // after filter ...
        return result;
    }
}

 

  1. 第一步是配置Dubbo Filter,有兩種方法,第一種是在配置文件裏面加入相關擴展配置,例如<dubbo:provider filter="xxx"/>。第二種方法是針對集合類擴展,好比:Filter, InvokerListener, ExportListener, TelnetHandler, StatusChecker等,能夠同時加載多個實現,使用@Activate的方式自動激活來簡化配置,如:    
    import com.alibaba.dubbo.common.extension.Activate;
    import com.alibaba.dubbo.rpc.Filter;
     
    @Activate(group = "provider", value = "xxx") // 只對提供方激活,group可選"provider"或"consumer"
    public class XxxFilter implements Filter {
        // ...
    }

     

  2. 第二步是建立配置文件,在resource/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter文件裏面寫配置xxx=com.xxx.XxxFilter
  3. 第三步是建立對應的Class,在配置對應的包下面新建一個XxxFilter,實現Dubbo的Filter接口。

經過以上3步就能夠自定義一個Filter框架

2.2 Dubbo是怎麼加載擴展

2.1.1 ExtensionLoader<T>

1) 建立:

這個類是用戶管理全部的dubbo擴展,是一個泛型,根據不一樣的擴展類(例如Filter,Protocol等),保存該類擴展的全部實現類。ide

例如,要想獲取每一個具體的ExtensionLoader,使用getExtensionLoader(Class) 來獲取,若是沒有就建立,若是以前建立過就返回以前建立的,邏輯比較簡單。ui

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    ... //省略參數校驗
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        //EXTENSION_LOADERS用於保存ExtensionLoader全部泛型實現子類
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

2)獲取:

獲取有效的擴展類,是經過getActivateExtension方法。代碼以下:this

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);
    if (! names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
        //加載擴展擴展的類
        getExtensionClasses();
        //加載@Activate註解的配置的擴展
        for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
            String name = entry.getKey();
            Activate activate = entry.getValue();
            if (isMatchGroup(group, activate.group())) {
                T ext = getExtension(name);
                if (! names.contains(name)
                        && ! names.contains(Constants.REMOVE_VALUE_PREFIX + name) 
                        && isActive(activate, url)) {
                    exts.add(ext);
                }
            }
        }
        Collections.sort(exts, ActivateComparator.COMPARATOR);
    }
    List<T> usrs = new ArrayList<T>();
    //加載 經過values傳遞過來的指定擴展
    for (int i = 0; i < names.size(); i ++) {
       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)) {
              if (usrs.size() > 0) {
              exts.addAll(0, usrs);
              usrs.clear();
              }
           } else {
           T ext = getExtension(name);
           usrs.add(ext);
           }
        }
    }
    if (usrs.size() > 0) {
       exts.addAll(usrs);
    }
    return exts;
}

傳入的參數說明:url

  • url-咱們知道dubbo的RPC調用相關信息都是經過URL形式保存的,所以url參數便是當前的某個服務調用
  • values-用來指定加載某些特殊的擴展,例如經過<dubbo:provider filter="xxx"/>來配置的過濾器,則在url中會有相關信息(service.filter),而後加載指定過濾器
  • group-用來處理@Activate註解的方式配置擴展

其中有一個比較重要的方法,getExtensionClasses(),原理就是初始化擴展名字及其對應的Class,在往裏面看

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

其中loadExtensionClasses方法以下:這裏看到了,爲何要配置在自定義過濾器的時候咱們須要配置META-INFO.dubbo.com.alibaba.dubbo.rpc.Filter這個文件,原來就是在這裏經過文件加載到ExtensionLoad中去的,而且路徑寫死在這裏。

private Map<String, Class<?>> loadExtensionClasses() {
    ...
    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    // META-INF/dubbo/internal/
    loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    // META-INF/dubbo/
    loadFile(extensionClasses, DUBBO_DIRECTORY);
    // META-INF/services/
    loadFile(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}

3) 使用

仍是以Filter爲例子,在哪裏調用Filter呢?首先要明白,dubbo的遠程調用都是經過抽象接口Invoker爲核心。ProtocolFilterWrapper的類中的buildInvokerChain方法用來建立Invoder的調用鏈。核心代碼以下:

private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
    Invoker<T> last = invoker;
    List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
    if (filters.size() > 0) {
        for (int i = filters.size() - 1; i >= 0; i --) {
            final Filter filter = filters.get(i);
            final Invoker<T> next = last;
            last = new Invoker<T>() {
                ...
                public Result invoke(Invocation invocation) throws RpcException {
                    return filter.invoke(next, invocation);
                }
               ...
            };
        }
    }
    return last;
}

這裏就使用到了ExtensionLoader的getActivateExtension方法獲取當前有效的Filter。

3. Spring Boot Style

在 https://my.oschina.net/u/3039671/blog/856577 文章中介紹了怎麼使用Spring boot來加載dubbo的相關bean,那麼就會想,對於dubbo的Filter,有沒有更優雅的聲明方式?例如Spring MVC中的聲明一個Filter就是直接聲明一個普通的Bean同樣。

仍是以Filter爲例子(注意,下面分析Filter是針對全局的Filter,即Provider或者Consumer層面Filter)

首先,擴展是從ExtensionLoad<Filter>中獲取,那麼咱們的目的就是在ExtensionLoad<Filter>中加入本身的Filter。上面說過,ExtensionLoad加載擴展有2種方式,一種是經過參數中Values來獲取,另一種是經過@Activate註解。簡單分析一下:

使用Values參數的方式

  1. 須要在聲明一個本身的Filter的時候,同時必須建立一個ProviderConfig或者ConsumerConfig(以代替<dubbo:provider filter="xxx"/>這樣的配置)
  2. 須要手動往ExtensionLoad中加入咱們本身的Filter(以代替經過讀取com.alibaba.dubbo.rpc.Filter配置加入到ExtensionLoad)

使用@Activate方式

  1. 使用@Activate的話,也要往ExtensionLoad中加入咱們本身的Filter,可是不用建立ProviderConfig或者ConsumerConfig。
  2. 必需要在本身的Filter上有@Activate註解。

這兩種方式都能達成咱們的需求,可是從開發難度來講使用@Activate註解相對簡單,並且第二點能夠經過其餘方式(proxy代理)來解決。所以下下面介紹基於@Activate註解來快速建立一個Dubbo的Filter。

3.1 實現方式

    自定義一個BeanPostProcessor,對每個Bean作如下處理:

3.2 完成之後的效果

這樣就能夠快速建立一個Dubbo Filter

@Bean
    ProviderFilter providerFilter(){
        return new ProviderFilter();
    }

    static class ProviderFilter extends AbstractDubboProviderFilterSupport {
        public Result invoke(Invoker<?> invoker, Invocation invocation) {
            System.out.println("ProviderFilter");
            return invoker.invoke(invocation);
        }
    }

若是有跟定製化的需求,可使用@Activate註解。

@Bean
    CustomFilter customFilter(){
        return new CustomFilter();
    }

    @Activate(group = Constants.PROVIDER)
    static class CustomFilter extends AbstractDubboFilterSupport {
        public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
            System.out.println("CustomFilter");
            return invoker.invoke(invocation);
        }

        public Filter getDefaultExtension() {
            return this;
        }
    }

 

注意,工程中的AnnotationBeanPostProcessor的實際上是對Dubbo中的AnnotationBean的修改,除了掃描Dubbo的@Service的註解方式修改了之外,主要是對遠程代理延時建立的邏輯。不然在@Reference建立代理的時候,咱們的Filter還沒建立,天然Filter就不能加入到Invoker調用鏈中。
相關文章
相關標籤/搜索