乐趣区

关于Dubbo泛化调用返回自定义异常实现

原因:使用 dubbo 的过程中遇到 provider 返回的自定义异常被包装, 在使用泛化调用的过程中,异常被包装成了GenericException(之前也在开发的过程中也遇到了,还不是太懂,没有去解决)

文章源码及部分讲解源自《深入理解 Apache Dubbo 实战》
dubbo version:2.7.1

在各种百度的过程中,和我预期的不太一样。找到了一篇看着还行的文章,先留着,看其他的文章时发现都是一模一样的,而且版本停留的比较前,不过关于 dubbo 的泛化调用也一直没怎么变。

Filter 简单描述

过滤器链初始化的实现原理

  1. 扩展点的初始化(SPI)
  2. 过滤器链组装:
    在服务暴露的过程中会使用 Protocol 层,ProtocolFilterWrapper实现组装。在暴露与引用的过程中,会使用 ProtocolFilterWrapper#buildInvokerChain 方法组装整个过滤器

    服务暴露与引用的过程

// 1- 服务暴露时会调用 buildInvokerChain
@Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {return protocol.export(invoker);
        }
        // Constants.PROVIDER-> 根据 Constants.PROVIDER 标识自己是提供者类型的调用链(group)
        return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
    }
    // 引用远程服务的时候也会调用 buildInvokerChain
    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {return protocol.refer(type, url);
        }
        // Constants.CONSUMER-> 根据 Constants.CONSUMER 标识消费者类型的调用链(group)
        return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
    }

#### 构造调用链源码

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);
                // 把 last 节点变成 next 节点,并放到 Filter 链的 next 中
                final Invoker<T> next = last;
                last = new Invoker<T>() {
                    ···省略
                    @Override
                    public Result invoke(Invocation invocation) throws RpcException {
                        // 设置过滤器链的下一个节点,不断循环形成过滤链
                        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;
    }

GenericFilter

用于服务提供者端,实现泛化调用,实现序列化的检查和处理

        Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
        if (result.hasException()
                && !(result.getException() instanceof GenericException)) {return new RpcResult(new GenericException(result.getException()));
        }

上述代码是实现 Filter#invoke 方法,在执行完后会判断 Result 中是否有异常,若不是 GenericException 会包装为 GenericException 异常再返回。

我在提供者做了自定义异常的处理,但是被包装了,并不能直接返回到前端被捕获。

下面我加入了判断:

             if (result.hasException()
                && !(result.getException() instanceof GenericException)) {
            // 处理自定义异常
            String className = result.getException().getClass().getName();
            if (className.startsWith("com.changyuan.education.commons.exception")) {
                // 打印堆栈
                result.getException().printStackTrace();
                // 返回异常信息
                return new RpcResult(new ResultBean().failure(result.getException().toString()));
            }
            return new RpcResult(new GenericException(result.getException()));
        }
  1. new ResultBean() 自定义的返回前端的对象,包含 code,msg,data
  2. result.getException().toString() 获取我抛出的自定义异常的内容,为了与 ResultBean 元素一样包含 code,msg
 // 重写的 toString,转为 json 对象
 @Override
    public String toString() {return "{\"code\":" + code + ",\"msg\":\"" + msg + "\"}";
    }
  1. resultBean()中接受 json 并解析, 最后打印完异常抛出给前端
    public ResultBean<T> failure(String upExcetionJson) {JSONObject jsonObject = JSON.parseObject(upExcetionJson);
        this.code = (int) jsonObject.get("code");
        this.msg = jsonObject.get("msg");
        return this;
    }
  1. 对于其他的 java 的异常先放着,后续解决,先把已知的交互异常处理

Dubbo2GenericFilter 自定义的 SPI

  1. 先创建这么一个过滤器
@Activate(group = Constants.PROVIDER, order = -20000)
public class Dubbo2GenericFilter implements Filter {}
  1. 在 resources 中创建文件夹META-INF.dubbo.internal, 创建文件org.apache.dubbo.rpc.Filter,写入一下内容

    generic=com.changyuan.education.exam.filter.Dubbo2GenericFilter

扩展点的配置和规范

Dubbo SPI 和 Java SPI 类似,需要在 META-INF/dubbo/ 下放置对应的 SPI 配置文件,文件名称必须命名为接口的全路径名。配置文件的内容为 key= 扩展点实现类的全路径名,多个实现用换行符隔开。其中 key 会被作为 Dubbo SPI 注解中传入的参数。Dubbo 会默认扫描三个文件夹 META-INF/dubbo/、META-INF/services/、META-INF/dubbo/internal/

退出移动版