Spring Cloud中异常处理的套路

异常在Java中有两种分类:Error(OutOfMemoryError之类的咱们本身程序没法处理的很是严重的错误,Java推荐不catch,让程序随之崩溃)、Excepiton(NullPointerException之类的并不致命的错误,Java以为indicates conditions that a reasonable application might want to catch,推荐catch),本文如下内容涉及到的都是Exception。前端

本文会结合REST API与Spring的一些具体实践来探讨一下异常处理的套路。程序员

异常与异常处理机制的意义

若是想学习Java工程化、高性能及分布式、深刻浅出。微服务、Spring,MyBatis,Netty源码分析的朋友能够加个人Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给你们。web

关于异常是拿来干什么的,不少人老程序员认为就是拿来咱们Debug的时候排错的,固然这一点确实是异常机制很是大的一个好处,但异常机制包含着更多的意义。数据库

  • 关注业务实现。异常机制使得业务代码与异常处理代码能够分开,你能够将一些你调用数据库操做的代码写在一个方法里而只须要在方法上加上throw DB相关的异常。至于如何处理它,你能够在调用该方法的时候处理或者甚至选择不处理,而不是直接在该方法内部添加上if判断若是数据库操做错误该如何办,这样业务代码会很是混乱。
  • 统一异常处理。与上一点有所联系。我当前所在项目的实践是,自定义业务类异常,在Controller或Service中抛出,让后使用Spring提供的异常接口统一处理咱们本身在内部抛出的异常。这样一个异常处理架构就很是明了。
  • 程序的健壮性。若是没有异常机制,那么来了个对空对象的某方法调用怎么办呢?直接让程序挂掉?这使人没法接受,固然,咱们本身平时写的一些小的东西确实是这样,没有处理它,让后程序挂了。但在web框架中,能够利用异常处理机制捕获该异常并将错误信息传递给咱们而后继续处理下个请求。因此异常对于健壮性是很是有帮助的。

异常处理(又称为错误处理)功能提供了处理程序运行时出现的任何意外或异常状况的方法。异常处理使用 try、catch 和 finally 关键字来尝试可能未成功的操做,处理失败,以及在过后清理资源。后端

先分类(dian cai)吧

我把异常根据意义成三种:业务、系统、代码异常,不一样的异常采用不一样的处理方式。缓存

业务异常

1
2
3
4
5
6
7
8
9
@GetMapping("/{id}")
public ReservationDetail getDetail(@PathVariable String id) {
    ReservationDetail result = applicationService.getReservationDetail(id);
    if (result == null) {
        throw new InfoNotFoundExcepiton("reservation with id=" + id + " is not exist");
    }

    return result;
}

以上代码当没有查到数据的时候抛出一个InfoNotFoundExcepiton异常,查询一个信息但不存在,没有任何系统级别的错误发生,而是数据确实不存在,此时属于业务异常。这个例子比较局限,其余的场景可能有一个用户想访问某个API,可是没有权限,此时能够返回无权限的业务异常。网络

将全部业务异常抛出,并经过Spring提供的接口进行统一处理,要注意的是,返回码也是须要分别标示的,对于意义不一样的业务异常,对应的错误返回码也是须要被指定的:架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestControllerAdvice
public class ControllerAdvice {

    private static final Logger logger = LoggerFactory.getLogger(ControllerAdvice.class);

    @ExceptionHandler(Throwable.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResult handleOtherException(Throwable e) {
        RestControllerAdvice
        return new ErrorResult(ErrorCode.UNKNOWN, e.getMessage());
    }

    @ExceptionHandler(ResourceAccessException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorResult handleResourceNotFoundException(ResourceAccessException e) {
        logger.error(e.getMessage(), e);
        return new ErrorResult(ErrorCode.RESOURCE_NOT_FOUND, e.getMessage());
    }

}

这种异常处理方式我的认为在咱们代码中越多越好,若是能在代码中涵盖业务中的不少边界值,对于总体应用的健壮性提高有着很是大的帮助,而且对于前端来讲,前端能够根据此异常信息给予用户更加明确友好的错误提示app

1
2
3
4
5
携带错误码为500的错误请求:
{
    "message": "cannot find pre inspection base info by order id: 1",
    "error_code": "SERVICE_REQUEST_ERROR"
}

系统异常

若是想学习Java工程化、高性能及分布式、深刻浅出。微服务、Spring,MyBatis,Netty源码分析的朋友能够加个人Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给你们。框架

这种异常在调试时很是常见,要么是某个服务挂掉了,或者超时这样的状况,跟业务没有关系,也不是代码中的BUG致使的,这个时候咱们必须设计好一个预案去cover这种风险。

在微服务架构中,这种状况时有发生,如在我翻译过的这篇文章中提到的,使用Netflix Hystrix解决,在Spring Cloud中已经携带该模块。具体以下:

  • 网络超时 - 不会无限期等待并使用超时策略。这能够肯定资源不会被无限期被捆绑在一块儿
  • 限制未完成的请求的数量 - 对于客户端可使用特定服务的未完成请求数量强加一个上限。若是到达限制,提出额外的请求可能没有意义,这些尝试须要当即失败。
  • 使用断路器模式 - 跟踪成功和失败的数量。若是错误率超过预约的阈值,断路器跳闸,以便之后的尝试失败。若是不少请求失败,建议服务设为不可用,发请求也是没有意义的。但超时以后,客户端应该再次尝试,若是成功,关闭断路器。
  • 提供备用逻辑 - 当请求失败后执行备用逻辑。好比返回缓存数据或者默认值好比空的推荐。

那么问题来了,这些异常能够与其余异常分类统一格式返回给前端吗?

1
@ExceptionHandler(Throwable.class)

这行代码捕捉了全部的异常,包括Error级别的,这是根据特定项目需求来肯定的,因此即便是Error也须要记录下来,出错以后方便错误的排查。

代码异常

我把代码中存在的BUG叫作代码异常,与系统异常不一样的是,这种异常只能尽可能避免与预防。好比程序员没有考虑到的状况致使空指针异常、SQL语句编写错误致使SQLException。在线上环境是很是严重的错误,须要立马开hotfix分支去修的,由于没有编写对应的业务处理方式,最严重的后果可能致使某个用户扣了钱可是没有显示支付成功。

和系统异常同样,这些异常因为是Throwable异常类下的异常,因此会被返回给前端。

异常处理流程与规范

异常处理流程在微服务架构中可能会比直接向前端发送异常信息这个过程麻烦一些,如Service向BFF层级传递异常一级。

异常在服务之间的传递

API Gateway (with Zuul) => BFF => 某服务

因为BFF与服务之间是经过Feign链接,因此咱们须要本身统一一下错误格式成为业务相关的格式返回给前端而不是直接将细化某个Java异常类的所有异常信息交给前端。

在这张图中,在BFF中检测参数是否匹配,在Service中检测是否资源存在,若是在BFF中抛出异常,则将INVALID_PARAMETER异常返回给前端,若是在Service中抛出异常,则将SERVICE_REQUEST_ERROR返回给前端。也就是将异常作出简单的分类:业务异常、非业务异常,非业务异常中能够像上面分类同样继续分类。

约定返回格式

若是想学习Java工程化、高性能及分布式、深刻浅出。微服务、Spring,MyBatis,Netty源码分析的朋友能够加个人Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给你们。

先后端统一错误格式,须要规定以下:

  • 返回格式:JSON
  • 返回请求状态码:根据不一样请求对应的状态码意义返回
  • 返回具体格式以下
1
2
3
4
{
   "message": "reservation details doesn't exist with id: xxx",
   "errorCode": "SERVICE_REQUEST_ERROR",
}
相关文章
相关标签/搜索