异常在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的时候排错的,固然这一点确实是异常机制很是大的一个好处,但异常机制包含着更多的意义。数据库
异常处理(又称为错误处理)功能提供了处理程序运行时出现的任何意外或异常状况的方法。异常处理使用 try、catch 和 finally 关键字来尝试可能未成功的操做,处理失败,以及在过后清理资源。后端
我把异常根据意义成三种:业务、系统、代码异常,不一样的异常采用不一样的处理方式。缓存
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大型互联网技术的视频免费分享给你们。
先后端统一错误格式,须要规定以下:
1 2 3 4 |
{ "message": "reservation details doesn't exist with id: xxx", "errorCode": "SERVICE_REQUEST_ERROR", } |