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

powerttt 2019-11-15 03:30:09
原文地址:https://segmentfault.com/a/1190000020853216

原因:使用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/

声明:该文章系转载,转载该文章的目的在于更广泛的传递信息,并不代表本网站赞同其观点,文章内容仅供参考。

本站是一个个人学习和交流平台,网站上部分文章为网站管理员和网友从相关媒体转载而来,并不用于任何商业目的,内容为作者个人观点, 并不代表本网站赞同其观点和对其真实性负责。

我们已经尽可能的对作者和来源进行了通告,但是可能由于能力有限或疏忽,导致作者和来源有误,亦可能您并不期望您的作品在我们的网站上发布。我们为这些问题向您致歉,如果您在我站上发现此类问题,请及时联系我们,我们将根据您的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。