博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Dubbo如何正确捕获业务异常
阅读量:3557 次
发布时间:2019-05-20

本文共 6741 字,大约阅读时间需要 22 分钟。

    笔者所在的公司,项目正在重构,从一个SpringBoot项目往Dubbo上迁移,但在拆分后发现一个问题,服务消费者(后文用Consumer代替)无法正确捕获服务提供者(后文用provider代替)所抛出的非受检查异常。在未拆分之前,项目都是打成一个jar包运行,service层未处理的unchecked异常,在controller层捕获到后可以正常打印出异常的堆栈信息,方便开发人员快速定位哪行代码抛得异常,但是service和controller分离后,service层部署在provider端,controller部署在Consumer端,发现,如果provider抛了一个unchecked异常,例如NPE、 / by zero,发现不管在provider端还是在custom端都没有很清晰得打印出异常堆栈信息。比如:在provider端抛一个/ by zero异常端,在Consumer端只会打印一行异常信息

对,只有简简单单一行,无论怎样也不会定位出异常发生得位置,经过一翻百度,才知道Dubbo对抛出得异常用一个ExceptionFilter的类进行拦截,下面一起来分析这段源代码:

@Activate(    group = {"provider"})public class ExceptionFilter implements Filter {    private final Logger logger;    public ExceptionFilter() {        this(LoggerFactory.getLogger(ExceptionFilter.class));    }    public ExceptionFilter(Logger logger) {        this.logger = logger;    }    public Result invoke(Invoker
invoker, Invocation invocation) throws RpcException { try { //1 Result result = invoker.invoke(invocation); //2 if (result.hasException() && GenericService.class != invoker.getInterface()) { try { //3 Throwable exception = result.getException(); //4 if (!(exception instanceof RuntimeException) && exception instanceof Exception) { return result; } else { try { //5 Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes()); Class
[] exceptionClassses = method.getExceptionTypes(); Class[] arr$ = exceptionClassses; int len$ = exceptionClassses.length; for(int i$ = 0; i$ < len$; ++i$) { Class
exceptionClass = arr$[i$]; //6 if (exception.getClass().equals(exceptionClass)) { return result; } } } catch (NoSuchMethodException var11) { return result; } this.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); //7 String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface()); String exceptionFile = ReflectUtils.getCodeBase(exception.getClass()); if (serviceFile != null && exceptionFile != null && !serviceFile.equals(exceptionFile)) { String className = exception.getClass().getName(); if (!className.startsWith("java.") && !className.startsWith("javax.")) { return (Result)(exception instanceof RpcException ? result : new RpcResult(new RuntimeException(StringUtils.toString(exception)))); } else { return result; } } else { return result; } } } catch (Throwable var12) { this.logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + var12.getClass().getName() + ": " + var12.getMessage(), var12); return result; } } else { return result; } } catch (RuntimeException var13) { this.logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + var13.getClass().getName() + ": " + var13.getMessage(), var13); throw var13; } }}

如上代码所示,当产生异常时,会执行一个invoke方法对异常对于进行处理。

1、异常对象封装在Result中,invoker是service类中方法的一个代理(Dubbo中Consumer端调用provider端的方法时,其实调用的是一个代理方法,在此处不多介绍)

2、如果service接口没有实现GenericService接口,向下走

3、获取异常对象exception

4、如果异常是checked类型的异常,直接返回Result

5、如果在定义service的时候,throws了异常

6、如果service产生的的异常与service接口throws的异常一致时,返回Result

7、获取invoker接口所在的文件名serviceFile和产生的异常所在的文件名exceptionFile,如果异常包名不是以java.开头或javax.开头(不是JDK中的异常),将异常封装成一个RpcResult返回,否则直接返回。

 

所以,Dubbo项目中要正确捕获业务异常,而不是简简单单打印一行错误信息,要这样做:

方法1: 将该异常的包名以"java.或者"javax. " 开头

方法2: 使用受检异常(继承Exception)

方法3:不用异常,使用错误码

方法4:把异常放到api的jar包中,供provider和Consumer共享,也就是自定义一个Exception,继承RuntimeException并实现Serializable接口(因为将异常从provider端传递给consumer端是经过RPC传递的),目前,我是这么处理公司的业务异常的。

方法5:provider实现GenericService接口

方法6:service的接口throws 一个自定义异常,在service中throw一个该异常给provider,如下(RpcRuntime Exception是一个自定义异常)

@Override    public String selectByDeleteFlag(String name) throws RpcRuntimeException{        try{            int i = 1 / 0;        }catch (RpcRuntimeException e){            throw  e;        }        return "aaa";    }

如果满足上面方法中的一个,在custom端便可以打印出正常的异常堆栈信息,方便开发者快速定位异常,在公司中,我是通过上述第四种方式处理的,自定义异常,代码如下:

在api的jar包中自定义异常RpcRuntimeException并打jar包,继承RuntimeException实现Serializable接口

public class RpcRuntimeException extends RuntimeException implements Serializable {    public RpcRuntimeException() {    }    public RpcRuntimeException(String msg) {        super(msg);    }    public RpcRuntimeException(Throwable cause) {        super(cause);    }    public RpcRuntimeException(String message, Throwable cause) {        super(message, cause);    }}

在provider端用aop实现一个全局异常处理器,将service产生的RuntimeException包装成一个RpcRuntimeException并throws给调用端,也就是Consumer端。

@Aspect@Componentpublic class RpcRuntimeExceptionHandler {    private final Logger logger = LoggerFactory.getLogger(RpcRuntimeExceptionHandler.class);    /**     * service层的RuntimeException统一处理器     * 可以将RuntimeException分装成RpcRuntimeException抛给调用端处理     * 或自行处理     * @param exception     */    @AfterThrowing(throwing="exception",pointcut="execution(* com.banma.tongye.service.*.*(..))")    public void afterThrow(Throwable exception){        if(exception instanceof  RuntimeException){            logger.error(exception.getMessage());            throw new RpcRuntimeException(exception);        }        exception.printStackTrace();    }}

这样在Consumer端就可以正常打印异常堆栈信息了。

 

好了,本文给大家分享的内容就是这些,在接下来的时间里,我会陆续为大家分享java后端技术,敬请期待,加作者微信或关注作者的微信公众号,可以得到更多后端技术。

你可能感兴趣的文章
JPA关系
查看>>
4.spring注解和生命周期相关的(了解)
查看>>
3.spring 的纯注解配置
查看>>
4.Spring 整合 Junit
查看>>
安装配置 Kali Linux 笔记
查看>>
持久加密U盘安装 Kali Linux 笔记
查看>>
[ 笔 记 ] 主动信息收集_002
查看>>
[ CTF ] ssh私钥泄漏_笔记
查看>>
设计模式学习
查看>>
操作系统学习总结
查看>>
Java JSON字符串与自定义类/基本类型相互转换
查看>>
Java中时间戳和时间格式的转换
查看>>
Dubbo基础知识整理
查看>>
计算机网络知识整理
查看>>
Java基础知识
查看>>
操作系统知识整理
查看>>
实现自己的权限管理系统(二):环境配置以及遇到的坑
查看>>
实现自己的权限管理系统(四): 异常处理
查看>>
实现自己的权限管理系统(十):角色模块
查看>>
实现自己的权限管理系统(十二):权限操作记录
查看>>