Dubbo Filter用法详解

        Filter是Dubbo中使用较为频繁的组件,其作用在于对所指定的请求进行过滤,功能非常类似于AOP,可以实现诸如请求过滤器和全局异常捕获器等组件。本文首先会讲解Filter的用法,然后会从源码的角度讲解其实现原理。

1. 用法示例

        对于Filter的划分,根据其面向的对象不同,可以分为service端和consumer端;根据其所作用的范围的不同,则可以分为单个服务过滤器(单个service或reference)和全局过滤器(整个provider或consumer)。Filter的指定方式主要有三种:

  • 标签中使用filter属性来指定具体的filter名称,这种使用方式的作用级别只针对于所指定的某个provider或consumer;
  • 标签中使用filter属性来指定具体的filter名称,这种使用方式的作用级别针对于所有的provider或consumer;
  • 在所声明的实现了Filter接口的类上使用@Activate注解来标注,并且注解中的group属性指定为providerconsumer

        这里我们就用一个异常捕获器的示例,在provider端分别使用上述三种方式为大家讲解Filter的使用方式。首先我们需要创建一个实现了Filter接口的异常捕获器:

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 wrapException(BusinessException exception) {
    Map result = new HashMap<>();
    result.put("errorCode", exception.getErrorCode());
    result.put("errorMsg", exception.getMessage());
    return result;
  }
}

        在声明了异常处理器之后,我们需要在META-INF/dubbo目录下新建一个名称为org.apache.dubbo.rpc.Filter的文件,然后在该文件中以键值对的形式将上面的异常处理器添加进去,如:

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

        这么做的原因在于,Dubbo在加载过滤器的时候会在META-INF/dubbo目录下查找所有名称为org.apache.dubbo.rpc.Filter的文件,并且读取文件内容,将文件内容以键值对的形式保存下来。可以看到,通过这种方式,Dubbo就实现了将数据的加载过程与用户使用的过程进行解耦。用户只需要按照上述方式声明一个过滤器,然后在指定文件(一般是自己创建)中添加该过滤器即可,Dubbo会加载所有的这些指定名称的文件,这里的文件名其实就是所加载的类所实现的接口全限定名。上面的步骤只是声明了需要加载这些过滤器,但是如果针对不同的服务提供者或消费者进行差异化的过滤器指定则是需要在配置文件中进行的。如下分别是针对单个服务提供者和针对所有的服务提供者指定该过滤器的三种方式:





// 这种方式主要用在Filter实现类上,group属性表示当前类会针对所有的provider所使用
@Activate(group = Constants.PROVIDER)

        需要注意的是,上面的第一种和第二种方式中filter属性的值都是在前面配置文件中所使用的键名,第三种方式则不需要在配置文件中进行指定,而只需要在实现Filter接口的实现类上进行指定该注解即可,group字段表示该实现类所属的一个分组,这里是provider端。

2. 实现原理

        在Dubbo中,对于服务的调用,最终是将其抽象为一个Invoker进行的,而在抽象的过程中,Dubbo会获取配置文件中指定的所有实现了Filter接口的类,然后根据为其指定的key名称,将其组织成一条链。具体的代码在ProtocolFilterWrapper中:

public class ProtocolFilterWrapper implements Protocol {

  @Override
  public  Exporter export(Invoker 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  Invoker buildInvokerChain(final Invoker invoker, 
        String key, String group) {
    Invoker last = invoker;
    // 获取所有实现了Filter接口的子类,这里key是service.filter,也就是说,其对应的配置位置在
    // 标签的filter属性中。group是provider,这个参数指明了这些Filter中
    // 只有provider类型的Filter才会在这里被组装进来。
    // 从整体上看,如果在配置文件中通过filter属性指定了各个filter的名称,那么这里就会通过SPI
    // 读取指定文件中的Filter实现子类,然后取其中的provider组内的Filter将其返回,以便进行后续的组装
    List 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 next = last;
        last = new Invoker() {

          @Override
          public Class 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节点,从而达到一个链的传递工作。整体的节点调用关系可以用下图表示:

Dubbo Filter用法详解_第1张图片

        通过上面的图可以看出,Dubbo过滤器的整个调用过程都是通过Invoker驱动的,最终对外的表现就是一个Invoker的头结点对象,通过这种方式,Dubbo能够将整个调用过程都统一化到一个Invoker对象中。

3. 小结

        本文首先以一个示例对Dubbo的Filter的使用方式进行了讲解,然后从源码的角度对过滤器的组装过程进行了讲解,详细描述了组装后的责任链的调用过程。

转载于:https://my.oschina.net/zhangxufeng/blog/3065393

你可能感兴趣的:(Dubbo Filter用法详解)