理解dubbofilter的执行顺序

对dubbo filter执行顺序可以看下这篇。

背景

在处理网关泛化调用的异常时,需要在provider端将可读异常的message返回给调用方,方便展示。
现有工程中有处理异常的filter:

@Activate(group = Constants.PROVIDER, order=Integer.MIN_VALUE) 
@Slf4j
public class ExceptionHandler implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {

        Result result = invoker.invoke(invocation);

        if (result.hasException() && GenericService.class != invoker.getInterface()) {
            Throwable exception = result.getException();
            log.info("接口调用异常 class:{} method:{}, arg:{} exception:", invoker.getInterface().getName(), invocation.getMethodName(), invocation.getArguments(), exception);
            if (exception instanceof BizException) {
                return new RpcResult(JSON.toJSON(ResultUtil.error(((BizException) exception).getCode(), exception.getMessage())));
            }
            if (exception instanceof IllegalArgumentException || exception instanceof IllegalStateException) {
                return new RpcResult(JSON.toJSON(ResultUtil.error(400, exception.getMessage())));
            }
            if (exception instanceof NullPointerException) {
                return new RpcResult(JSON.toJSON(ResultUtil.error(500, StringUtils.isEmpty(exception.getMessage()) ? "服务器异常" : exception.getMessage())));
            }

            return new RpcResult(JSON.toJSON(ResultUtil.error(500, "服务器异常")));
        }
        return result;
    }
}

看上去没有问题,对rpcResult中的exception拿出来做异常类的判断,包括参数错误、服务器异常等常见错误。
但是在调用的时候却发现最后都会被包装成兜底的服务器异常。

原因及解决

debug发现,因为网关是泛化调用,所以走到此filter的异常都是GenericException,其中包了对应的业务异常。

public class GenericException extends RuntimeException {

	private static final long serialVersionUID = -1182299763306599962L;

	private String exceptionClass;

    private String exceptionMessage;
}

可以想到这里是因为Genericfilter。
GenericFilter会调用invoke.invoke,并且根据rpcResult包装对应的异常。
那么就是想到order设置问题。但是按照之前理解(一种错误的理解)order越小应该越
先执行,这里明明设置了MIN_VALUE,而GenericFilter的order是-20000,应该自定义filter先执行啊,为什么拿到的还是GenericException?

其实这种理解是错误的,order越小越先执行的结论是没错的,但是Dubbo filter链调用是通过调用包装过的invoker对象的next来实现的。来看ProtocolFilterWrapper中构建执行链的逻辑:

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.isEmpty()) {
            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 {
                        return filter.invoke(next, invocation);
                    }

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

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

当获取到有序的Activate的Filter,为当前的invoker的invoker方法封装上循环的filter。
来看一个自定义的Filter在export之后经过filter链封装的模型:

helloFilter模拟了上边的ExceptionFilter,order是MIN_VALUE,在服务端启动的时候export流程中拿到的activate的Filter实现(已排好序的)

dubbo filter 如何配置 dubbo filter 顺序_泛化

可以看到因为HelloFilter的order最小,所以排在了第一个。而看到上边ProtocolFilterWrapper中,从最后一个ExceptionFilter开始为invoker封装filter逻辑,这里new Invoker中实现的invoker方法就是按照order从大到小包装invoker。包装后的类型为ProtocolFilterWrapper$1内部类类型,其中有 filter类型的实例、真正的invoker实例(获取url等方法还是直接调用的原始的invoker)、还有next引用(一个ProtocolFilterWrapper$1实例)。

所以理解错误关键的点在这:order越小的Filter是越先被执行,但是在调用其invoke方法时,会调用next.invoke方法,也就是下一个filter的invoke方法。

dubbo filter 如何配置 dubbo filter 顺序_dubbo filter 如何配置_02

回到上面的问题,当我们自定义异常过滤器的order比GenericFilter的order小,所以过滤链为:

{
    "filter":"ExceptionFilter",
    "invoker":invoker实例,
    "next":{
        filter:"GenericFilter",
        "invoker":invoker实例,
        "next":...
    }
}

也就是调用ExceptionFilter的invoke方法时,会调用到GenericFilter的invoke方法,而GenericFilter处理异常的逻辑就是包装GenericException。

@Activate(group = Constants.PROVIDER, order = -20000)
public class GenericFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
        if (inv.getMethodName().equals(Constants.$INVOKE)
                && inv.getArguments() != null
                && inv.getArguments().length == 3
                && !ProtocolUtils.isGeneric(invoker.getUrl().getParameter(Constants.GENERIC_KEY))) {
                // 这里的判断是泛化调用的判断 
                // 参数长度为3 是因为泛化调用的参数为接口、参数类型、参数实例
                
                // 省略代码
                Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
                if (result.hasException()
                        && !(result.getException() instanceof GenericException)) {
                    return new RpcResult(new GenericException(result.getException()));
                }
                if (ProtocolUtils.isJavaGenericSerialization(generic)) {
                    try {
                        UnsafeByteArrayOutputStream os = new UnsafeByteArrayOutputStream(512);
                        ExtensionLoader.getExtensionLoader(Serialization.class)
                                .getExtension(Constants.GENERIC_SERIALIZATION_NATIVE_JAVA)
                                .serialize(null, os).writeObject(result.getValue());
                        return new RpcResult(os.toByteArray());
                    } catch (IOException e) {
                        throw new RpcException("Serialize result failed.", e);
                    }
                } else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
                    return new RpcResult(JavaBeanSerializeUtil.serialize(result.getValue(), JavaBeanAccessor.METHOD));
                } else {
                    return new RpcResult(PojoUtils.generalize(result.getValue()));
                }
            } catch (NoSuchMethodException e) {
                throw new RpcException(e.getMessage(), e);
            } catch (ClassNotFoundException e) {
                throw new RpcException(e.getMessage(), e);
            }
        }
        return invoker.invoke(inv);
    }

}

这也解释了为什么在自定义的ExceptionFilter拿到的是GenericException,不好去解析可读异常。

解决办法也很简单:

  1. 在自定义异常过滤器中对GenericException做拆分操作,判断真正的业务异常和可读message。
  2. 将自定义异常过滤器的order调整比GenericFilter大,这样当完成异常拦截之后,返回的RpcResult中无异常信息,GenericFilter也不会去封装异常,则此次泛化调用只是调用了下,没有实质操作。