一些实用异常处理

异常的应用场景

  • Repository层出现异常或许可以忽略,或许可以降级,或许需要转化为一个友好的异常。如果一律捕获异常仅记录日志,很可能业务逻辑已经出错,而用户和程序本身完全感知不到。
  • Service层往往涉及数据库事务,出现异常同样不适合捕获,否则事务无法自动回滚。此外Service层涉及业务逻辑,有些业务逻辑执行中遇到业务异常,可能需要在异常后转入分支业务流程。如果业务异常都被框架捕获了,业务功能就会不正常。
  • 如果下层异常上升到Controller层还是无法处理的话。Controller层往往会给予用户友好提示,或是根据每一个API的异常表返回指定的异常类型,同样无法对所有异常一视同仁。

业务代码层面考虑异常处理

对于自定义的业务异常,以 warn 级别的日志记录异常以及当前 URL、执行方法等信息后,提取异常中的错误码和消息等信息,转换为合适的 API 包装体 返回给 API 调用方;

对于无法处理的系统异常,以 Error 级别的日志记录异常和上下文信息(比如 URL、参数、用户 ID)后,转换为普适的“服务器忙, 请稍后再试”异常信息,同样以 API 包装体返回给调用方。

@RestControllerAdvice
@Slf4j
public class RestControllerExceptionHandler {
    private static int GENERIC_SERVER_ERROR_CODE = 2000;
    private static String GENERIC_SERVER_ERROR_MESSAGE = "服务器忙,请稍后再试";
@ExceptionHandler
public APIResponse handle(HttpServletRequest req, HandlerMethod method, Exception ex) {
    if (ex instanceof BusinessException) {
        BusinessException exception = (BusinessException) ex;
        log.warn(String.format("访问 %s -> %s 出现业务异常!", req.getRequestURI(),                     
         method.toString()), ex);
    return new APIResponse(false, null, exception.getCode(), exception.getMessage());
    }else {
        log.error(String.format("访问 %s -> %s 出现系统异常!", req.getRequestURI(),     
        method.toString()), ex);
        return new APIResponse(false, null, GENERIC_SERVER_ERROR_CODE, GENERIC_SERVER_ERROR_MESSAGE);
        }
     }
}

禁止“生吞”异常

在任何时候,我们捕获了异常都不应该生吞,也就是直接丢弃异常不记录、不抛出。

原因:这样的处理方式还不如不捕获异常,因为被生吞掉的异常一旦导致 Bug,就很难在程序中找到蛛丝马迹,使得 Bug 排查工作难上加难。通常情况下,生吞异常的原因,可能是不希望自己的方法抛出受检异常,只是为了 把异常“处理掉”而捕获并生吞异常,也可能是想当然地认为异常并不重要或不可能产生。但不管是什么原因,不管是你认为多么不重要的异常,都不应该生吞,哪怕是一个日志也好。 

丢弃异常的原始信息

错误示例

private void readFile() throws IOException {
    Files.readAllLines(Paths.get("a_file"));
}
@GetMapping("wrong1")
public void wrong1(){
    try {
        readFile();
        } catch (IOException e) {
             //原始异常信息丢失
             throw new RuntimeException("系统忙请稍后再试");
             }
        }
        //或者是这样也不行
          catch (IOException e) {
         //只保留了异常消息,栈没有记录
         log.error("文件读取错误, {}", e.getMessage());
         throw new RuntimeException("系统忙请稍后再试");
}

我们的日志就成了这个样子,无法判断出这个是什么错
[12:57:19.746] [http-nio-45678-exec-1] [ERROR] [.g.t.c.e.d.HandleExceptionController:35 ] - 文件读取错误, a_file

正确做法

catch (IOException e) {
    log.error("文件读取错误", e);
    throw new RuntimeException("系统忙请稍后再试");
}
//或者
catch (IOException e) {
    throw new RuntimeException("系统忙请稍后再试", e);
}

抛出异常时不指定任何消息

throw new RuntimeException();

[13:25:18.031] [http-nio-45678-exec-3] [ERROR] [c.e.d.RestControllerExceptionHandler:24 ] - 访问 /handleexception/wrong3 ->
org…..demo1.HandleExceptionController#wrong3(String) 出现系统异常!
java.lang.RuntimeException: null
...
这里的 null 非常容易引起误解。按照空指针问题排查半天才发现,其实是异常的 message 为空。

finally 中的异常处理

错误做法

@GetMapping("wrong")
public void wrong() {
    try {
        log.info("try");
        //异常丢失
        throw new RuntimeException("try");
    } finally {
        log.info("finally");
        throw new RuntimeException("finally");
    }
}

正确做法

@GetMapping("right")
public void right() {
    try {
        log.info("try");
        throw new RuntimeException("try");
        } finally {
             log.info("finally");
             try {
                 throw new RuntimeException("finally");
                 } catch (Exception ex) {
                     log.error("finally", ex);
                    }
                 }
}
//或者
@GetMapping("right2")
public void right2() throws Exception {
     Exception e = null;
     try {
         log.info("try");
         throw new RuntimeException("try");
         } catch (Exception ex) {
             e = ex;
             } finally {
                 log.info("finally");
                 try {
                     throw new RuntimeException("finally");
                     } catch (Exception ex) {
                         if (e!= null) {
                             e.addSuppressed(ex);
                         } else {
                             e = ex;
                           }
                       }
                   }
             throw e;
}

业务异常的定义

使用业务异常类进行异常的规范化处理

public class OrderExceptions {
    public static BusinessException orderExists(){
        return new BusinessException("订单已经存在", 3001);
    }
    public static BusinessException orderCanceled(){
        return new BusinessException("订单已经取消", 3002);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值