DK's Notes DK's Notes
首页
导航站
  • Java-Se

    • Java基础
  • Java-Se进阶-多线程

    • 多线程
  • Java-Se进阶-java8新特性

    • java8新特性
  • Java-ee

    • JavaWeb
  • Java虚拟机

    • JVM
  • SQL 数据库

    • MySQL
  • NoSQL 数据库

    • Redis
    • ElasticSearch
    • MongoDB
  • ORM

    • MyBatis
    • MyBatis-Plus
  • Spring

    • Spring
  • SpringMVC

    • SpringMVC1
    • SpringMVC2
  • SpringCloud

    • SpringCloud
  • 中间件

    • RabbitMQ
    • Dubbo
  • 秒杀项目
  • Git
  • Linux
  • Docker
  • JWT
  • 面试
  • 刷题
开发问题😈
设计模式
关于💕
归档🕛
GitHub (opens new window)

风

摸鱼
首页
导航站
  • Java-Se

    • Java基础
  • Java-Se进阶-多线程

    • 多线程
  • Java-Se进阶-java8新特性

    • java8新特性
  • Java-ee

    • JavaWeb
  • Java虚拟机

    • JVM
  • SQL 数据库

    • MySQL
  • NoSQL 数据库

    • Redis
    • ElasticSearch
    • MongoDB
  • ORM

    • MyBatis
    • MyBatis-Plus
  • Spring

    • Spring
  • SpringMVC

    • SpringMVC1
    • SpringMVC2
  • SpringCloud

    • SpringCloud
  • 中间件

    • RabbitMQ
    • Dubbo
  • 秒杀项目
  • Git
  • Linux
  • Docker
  • JWT
  • 面试
  • 刷题
开发问题😈
设计模式
关于💕
归档🕛
GitHub (opens new window)
  • mybatis

  • mybatis-plus

  • Spring

  • SpringMvc

  • RabbitMQ

  • Dubbo

    • Dubbo知识体系
    • Dubbo
    • 服务注册(provider服务暴露)
    • 服务发现(consumer服务引入)
    • 服务调用过程
    • SPI机制
    • 负载均衡机制
    • 服务容错、降级
    • Dubbo的服务异常处理
      • 起因
      • 源码 org.apache.dubbo.rpc.filter.ExceptionFilter
      • 能正常抛出的情况总结
      • 处理方案
  • SpringCloud

  • 框架
  • Dubbo
zdk
2022-08-12
目录

Dubbo的服务异常处理

Table of Contents generated with DocToc (opens new window)

  • Dubbo异常处理
    • 起因
    • 源码 org.apache.dubbo.rpc.filter.ExceptionFilter
    • 能正常抛出的情况总结
    • 处理方案

# Dubbo异常处理

# 起因

按我们非rpt式的写法,在provider的serviceImpl中,抛出一个业务异常BusinessException(继承RuntimeException),但是我们会发现,在consumer端接收到的异常信息只是RuntimeException,只是message是我们自己的信息,其他的都只是一个普通的RuntimeException,这里就开始疑惑,为什么Dubbo服务端不能识别我们自定义的异常类型呢?

# 源码 org.apache.dubbo.rpc.filter.ExceptionFilter

@Activate(group = CommonConstants.PROVIDER)
public class ExceptionFilter implements Filter, Filter.Listener {
    private Logger logger = LoggerFactory.getLogger(ExceptionFilter.class);

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        return invoker.invoke(invocation);
    }

    @Override
    public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
        //响应入口开始
        
        if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
            //上面的语句的意思是  如果有异常 且没有实现GenericService这个接口则进入下一步 否则不执行任何操作
            try {
                Throwable exception = appResponse.getException();

                // directly throw if it's checked exception
                // 如果不是 运行时异常 或者 受检查异常  直接返回
                if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
                    return;
                }
                // directly throw if the exception appears in the signature
                // 如果调用的这个方法上面  有throws xxException 签名 直接抛出这个签名的异常
                try {
                    Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
                    Class<?>[] exceptionClassses = method.getExceptionTypes();
                    for (Class<?> exceptionClass : exceptionClassses) {
                        if (exception.getClass().equals(exceptionClass)) {
                            return;
                        }
                    }
                } catch (NoSuchMethodException e) {
                    return;
                }

                // for the exception not found in method's signature, print ERROR message in server's log.
                logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);

                // directly throw if exception class and interface class are in the same jar file.
                // 这里是如果异常类和接口类在同一个jar包中,直接抛出
                String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
                String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
                    return;
                }
                // directly throw if it's JDK exception
                // 如果这个异常是jdk的异常  直接抛出
                String className = exception.getClass().getName();
                if (className.startsWith("java.") || className.startsWith("javax.")) {
                    return;
                }
                // directly throw if it's dubbo exception
                // 如果是Dubbo自定义的这个RpcException 直接抛出
                if (exception instanceof RpcException) {
                    return;
                }

                // otherwise, wrap with RuntimeException and throw back to the client
                // 都不是以上几种情况的时候 只把message保留  然后封装为一个RuntimeException抛出
                appResponse.setException(new RuntimeException(StringUtils.toString(exception)));
            } catch (Throwable e) {
                logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
            }
        }
    }

    @Override
    public void onError(Throwable e, Invoker<?> invoker, Invocation invocation) {
        logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
    }

    // For test purpose
    public void setLogger(Logger logger) {
        this.logger = logger;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

# 能正常抛出的情况总结

  1. 首先,服务类不能实现GenericService接口,否则不会处理异常

    image-20220812172005562

  2. 抛出的异常得是RuntimeException或受检异常 及其子类

    • 自定义异常通常是RuntimeException

    image-20220812172436335

  3. Impl方法上有throws xxException 签名,可以直接抛出

    • 作为生产服务端,不应显式抛出异常给客户的进行处理,所以也不适用

    image-20220812172451910

  4. 调用的接口类和抛出的异常类在同一个jar包中,可以直接抛出

    • 较大的项目一般都会有一些common包,定义好异常类型,使用二方包的方式引用,所以也不适用

    image-20220812172515160

  5. 全类名以java.或javax.开头,可以直接抛出

    • 这个方案不现实,也不符合规范,所以不采用

    image-20220812172529144

  6. 抛出的异常是RpcException及其子类,可以直接抛出

    image-20220812172546687

其他的情况,就只用发现的异常的message封装一个RuntimeException返回

image-20220812172623283

# 处理方案

最好的方案是重写一下这个Filter,加上处理业务异常的逻辑

  1. 将ExceptionFilter copy 到自己的项目改名为DubboExceptionFilter或者新建一个Filter 实现org.apache.dubbo.rpc.Filter接口

    @Activate(group = Constants.PROVIDER)
    public class DubboExceptionFilter implements Filter {
    
        private final Logger logger;
    
        public DubboExceptionFilter() {
            this(LoggerFactory.getLogger(DubboExceptionFilter.class));
        }
    
        public DubboExceptionFilter(Logger logger) {
            this.logger = logger;
        }
    
        @Override
        public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
            try {
                Result result = invoker.invoke(invocation);
                if (result.hasException() && GenericService.class != invoker.getInterface()) {
                    try {
                        Throwable exception = result.getException();
    
                        // directly throw if it's checked exception
                        if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
                            return result;
                        }
                        // directly throw if the exception appears in the signature
                        try {
                            Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
                            Class<?>[] exceptionClassses = method.getExceptionTypes();
                            for (Class<?> exceptionClass : exceptionClassses) {
                                if (exception.getClass().equals(exceptionClass)) {
                                    return result;
                                }
                            }
                        } catch (NoSuchMethodException e) {
                            return result;
                        }
    
                        // for the exception not found in method's signature, print ERROR message in server's log.
                        logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
                                + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
                                + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
    
                        // directly throw if exception class and interface class are in the same jar file.
                        String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
                        String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                        if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
                            return result;
                        }
                        // directly throw if it's JDK exception
                        String className = exception.getClass().getName();
                        if (className.startsWith("java.") || className.startsWith("javax.")) {
                            return result;
                        }
                        // 在此处指定我们要处理的异常类的包名前缀
                        if (className.startsWith("com.zdk.xx.exception")) {
                            return result;
                        }
                        // directly throw if it's dubbo exception
                        if (exception instanceof RpcException) {
                            return result;
                        }
    
                        // otherwise, wrap with RuntimeException and throw back to the client
                        return new RpcResult(new RuntimeException(StringUtils.toString(exception)));
                    } catch (Throwable e) {
                        logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost()
                                + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
                                + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
                        return result;
                    }
                }
                return result;
            } catch (RuntimeException e) {
                logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
                        + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
                        + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
                throw e;
            }
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
  2. 指定我们要处理的异常类的包名前缀,避免被Dubbo封装为RpcException

    // 在此处指定我们要处理的异常类的包名前缀
    if (className.startsWith("com.zdk.xx.exception")) {
        return result;
    }
    
    1
    2
    3
    4
  3. 在resources目录下添加纯文本文件META-INF/dubbo/com.alibaba.dubbo.rpc.Filter并添加内容

    dubboExceptionFilter=com.dliberty.core.exception.DubboExceptionFilter
    
    1

    image-20220812173201945

    这里利用的是Dubbo的自定义扩展机制

  4. 修改dubbo 的配置文件,将DubboExceptionFilter加载进去并且去掉自身的ExceptionFilter

    <dubbo:provider filter="dubboExceptionFilter,-exception" ></dubbo:provider>
    
    1

这样就完成了对业务异常的完美处理了

在 GitHub 上编辑此页 (opens new window)
#Dubbo
最后更新: 2022/10/04, 8:10:00
服务容错、降级
SpringCloud知识体系

← 服务容错、降级 SpringCloud知识体系→

Theme by Vdoing | Copyright © 2022-2023 zdk | notes
湘ICP备2022001117号-1
川公网安备 51142102511562号
提供加速服务
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式