Dubbo Filter用法詳解

        Filter是Dubbo中使用較爲頻繁的組件,其做用在於對所指定的請求進行過濾,功能很是相似於AOP,能夠實現諸如請求過濾器和全局異常捕獲器等組件。本文首先會講解Filter的用法,而後會從源碼的角度講解其實現原理。java

1. 用法示例

        對於Filter的劃分,根據其面向的對象不一樣,能夠分爲service端和consumer端;根據其所做用的範圍的不一樣,則能夠分爲單個服務過濾器(單個service或reference)和全局過濾器(整個provider或consumer)。Filter的指定方式主要有三種:apache

  • 在<dubbo:service filter=""/>或<dubbo:reference filter=""/>標籤中使用filter屬性來指定具體的filter名稱,這種使用方式的做用級別只針對於所指定的某個provider或consumer;
  • 在<dubbo:provider filter=""/>或<dubbo:consumer filter=""/>標籤中使用filter屬性來指定具體的filter名稱,這種使用方式的做用級別針對於全部的provider或consumer;
  • 在所聲明的實現了Filter接口的類上使用@Activate註解來標註,而且註解中的group屬性指定爲providerconsumer

        這裏咱們就用一個異常捕獲器的示例,在provider端分別使用上述三種方式爲你們講解Filter的使用方式。首先咱們須要建立一個實現了Filter接口的異常捕獲器:app

public class ExceptionResolver implements Filter {

  @Override
  public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    Result result = invoker.invoke(invocation);	// 進行服務調用
    if (result.getException() instanceof BusinessException) {	// 判斷拋出的異常是否爲業務異常
      BusinessException exception = (BusinessException) result.getException();
      result = new RpcResult(wrapException(exception));	// 若是是業務異常,則對異常結果進行封裝返回
    }

    return result;
  }

  // 封裝業務異常
  private Map<String, Object> wrapException(BusinessException exception) {
    Map<String, Object> result = new HashMap<>();
    result.put("errorCode", exception.getErrorCode());
    result.put("errorMsg", exception.getMessage());
    return result;
  }
}

        在聲明瞭異常處理器以後,咱們須要在META-INF/dubbo目錄下新建一個名稱爲org.apache.dubbo.rpc.Filter的文件,而後在該文件中以鍵值對的形式將上面的異常處理器添加進去,如:async

exceptionResolver=org.apache.dubbo.demo.example.eg4.ExceptionResolver

        這麼作的緣由在於,Dubbo在加載過濾器的時候會在META-INF/dubbo目錄下查找全部名稱爲org.apache.dubbo.rpc.Filter的文件,而且讀取文件內容,將文件內容以鍵值對的形式保存下來。能夠看到,經過這種方式,Dubbo就實現了將數據的加載過程與用戶使用的過程進行解耦。用戶只須要按照上述方式聲明一個過濾器,而後在指定文件(通常是本身建立)中添加該過濾器便可,Dubbo會加載全部的這些指定名稱的文件,這裏的文件名其實就是所加載的類所實現的接口全限定名。上面的步驟只是聲明瞭須要加載這些過濾器,可是若是針對不一樣的服務提供者或消費者進行差別化的過濾器指定則是須要在配置文件中進行的。以下分別是針對單個服務提供者和針對全部的服務提供者指定該過濾器的三種方式:ide

<!-- 這種方式只會針對DemoService這一個服務提供者使用該過濾器 -->
<dubbo:service interface="org.apache.dubbo.demo.example.eg4.DemoService" ref="demoService" filter="exceptionResolver"/>
<!-- 這種方式會針對全部的provider服務提供者使用該過濾器 -->
<dubbo:provider filter="exceptionResolver"/>
// 這種方式主要用在Filter實現類上,group屬性表示當前類會針對全部的provider所使用
@Activate(group = Constants.PROVIDER)

        須要注意的是,上面的第一種和第二種方式中filter屬性的值都是在前面配置文件中所使用的鍵名,第三種方式則不須要在配置文件中進行指定,而只須要在實現Filter接口的實現類上進行指定該註解便可,group字段表示該實現類所屬的一個分組,這裏是provider端。ui

2. 實現原理

        在Dubbo中,對於服務的調用,最終是將其抽象爲一個Invoker進行的,而在抽象的過程當中,Dubbo會獲取配置文件中指定的全部實現了Filter接口的類,而後根據爲其指定的key名稱,將其組織成一條鏈。具體的代碼在ProtocolFilterWrapper中:code

public class ProtocolFilterWrapper implements Protocol {

  @Override
  public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
      return protocol.export(invoker);
    }
    
    // 進行服務導出時會會經過buildInvokerChain()方法查找全部實現了Filter接口的子類,
    // 將其按照必定的順序組裝爲一個Filter鏈
    return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, 
       Constants.PROVIDER));
  }

  private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, 
        String key, String group) {
    Invoker<T> last = invoker;
    // 獲取全部實現了Filter接口的子類,這裏key是service.filter,也就是說,其對應的配置位置在
    // <dubbo:service/>標籤的filter屬性中。group是provider,這個參數指明瞭這些Filter中
    // 只有provider類型的Filter纔會在這裏被組裝進來。
    // 從總體上看,若是在配置文件中經過filter屬性指定了各個filter的名稱,那麼這裏就會經過SPI
    // 讀取指定文件中的Filter實現子類,而後取其中的provider組內的Filter將其返回,以便進行後續的組裝
    List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class)
      .getActivateExtension(invoker.getUrl(), key, group);
    if (!filters.isEmpty()) {
      // 這裏的整個動做其實就是對鏈的一個組裝,好比經過上面的步驟獲取到了三個Filter:A、B和C。
      // 在這裏會爲每個子類都聲明一個Invoker對象,將該對象的invoke()方法委託給鏈的下一個節點。
      // 這樣,經過不斷的委託動做,在遍歷完成以後,就會獲得一個Invoker的頭結點,最後將頭結點返回。
      // 這樣就達到了組裝Invoker鏈的目的
      for (int i = filters.size() - 1; i >= 0; i--) {
        final Filter filter = filters.get(i);
        final Invoker<T> next = last;
        last = new Invoker<T>() {

          @Override
          public Class<T> getInterface() {
            return invoker.getInterface();
          }

          @Override
          public URL getUrl() {
            return invoker.getUrl();
          }

          @Override
          public boolean isAvailable() {
            return invoker.isAvailable();
          }

          @Override
          public Result invoke(Invocation invocation) throws RpcException {
            // filter指向的是當前節點,而傳入的Invoker參數是其下一個節點
            Result result = filter.invoke(next, invocation);
            if (result instanceof AsyncRpcResult) {
              AsyncRpcResult asyncResult = (AsyncRpcResult) result;
              asyncResult.thenApplyWithContext(r -> 
                  filter.onResponse(r, invoker, invocation));
              return asyncResult;
            } else {
              return filter.onResponse(result, invoker, invocation);
            }
          }

          @Override
          public void destroy() {
            invoker.destroy();
          }

          @Override
          public String toString() {
            return invoker.toString();
          }
        };
      }
    }
    return last;
  }
}

        上面的組裝過程,從總體上來看,其實就是對獲取到的Filter從尾部開始遍歷,而後依次爲該節點建立一個Invoker對象,由該Invoker對象調用該Filter節點,從而達到一個鏈的傳遞工做。總體的節點調用關係能夠用下圖表示:xml

過濾器

        經過上面的圖能夠看出,Dubbo過濾器的整個調用過程都是經過Invoker驅動的,最終對外的表現就是一個Invoker的頭結點對象,經過這種方式,Dubbo可以將整個調用過程都統一化到一個Invoker對象中。對象

3. 小結

        本文首先以一個示例對Dubbo的Filter的使用方式進行了講解,而後從源碼的角度對過濾器的組裝過程進行了講解,詳細描述了組裝後的責任鏈的調用過程。blog

相關文章
相關標籤/搜索