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

相关文章
相关标签/搜索