基于spring boot实现一个全局异常处理器
作者:Java小卷
还记得前面我们的实现逻辑,在service
层如果有校验失败我们会抛出异常,而controller
中我们对其并没有处理,实际上spring boot有自己全局的错误处理形式。我们可以基于spring boot提供的切面特性,来很轻松的实现全局异常的处理,现在我们约定,只要后台逻辑处理失败的情况,我们都将抛出异常,包括controller
中的处理逻辑。
实现全局异常处理器
现在我们就来编写全局异常处理器。
package com.xiaojuan.boot.common.web.support; import ... import static com.xiaojuan.boot.common.enums.BusinessError.*; @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { /** 维护一个错误码与http状态码的映射 */ private Map<Integer, HttpStatus> httpStatusMap; @PostConstruct private void init() { httpStatusMap = new HashMap<>(); httpStatusMap.put(NO_LOGIN.getValue(), HttpStatus.UNAUTHORIZED); httpStatusMap.put(HAS_NO_ROLE.getValue(), HttpStatus.FORBIDDEN); } @ExceptionHandler(BusinessException.class) public ResponseEntity<Response<?>> handleException(BusinessException ex) { log.error(ex.getMessage(), ex); // 默认服务器端错误,返回500状态码 HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; if (!StringUtils.isEmpty(ex.getErrCode()) && httpStatusMap.containsKey(ex.getErrCode())) { status = httpStatusMap.get(ex.getErrCode()); } return ResponseEntity.status(status).body(Response.fail(ex.getMessage(), ex.getErrCode(), ex.getData())); } @ExceptionHandler(Exception.class) public ResponseEntity<Response<?>> handleException(Exception ex) { log.error(ex.getMessage(), ex); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Response.fail("小卷生鲜电商系统异常:" + ex.getMessage())); } }
代码详解
这里我们用了@RestControllerAdvice
注解对所有的@RestController
修饰的controller
都进行拦截,只要抛出了异常,就交给这个处理器来处理。
这里要说下响应的http状态码,一般都返回200
,某些情况下我们将返回一个可以让前端特别处理的状态码,比如用户未登录的请求被拦截了我们可以返回401
,用户登录了却没有权限的操作,我们返回403
,这样前端对这块直接从http状态就能识别请求结果的状态。而对于我们业务开发来说,关注的是业务处理的错误码,在这里我们和请求http状态码之间做了一个映射转换,维护在httpStatusMap
成员变量中。
接下来,我们写了两个异常处理方法,它们都必须用@ExceptionHandler
注解来指定我们要处理的异常的类型,这里我们只考虑两种异常:我们自定义的业务异常BusinessException
和最大的Exception
。
对于BusinessException
,我们要考虑HttpStatus
的判断逻辑,如果我们将某些业务错误码和http状态码做了映射,那么对于这些业务错误码我们就取映射到的http状态码来返回,否则我们返回服务器端错误的500
状态码。
注意处理方法最后的返回结果,我们使用的是ResponseEntity<Response<?>>
,spring web模块提供的ResponseEntity
可以帮助我们按照指定的http状态码并解析指定格式的内容体(Spring Boot默认配置json形式)进行前端响应。而要响应的对象就是我们之前定义的Response
,只不过我们将字段errCode
的类型从原来的String
改成了Integer
,因为业务错误码我们就定义为数值型,同样的还有BusinessException
类中的errCode
字段类型的调整。
除了BusinessException
,我们只处理最大的Exception
,http状态码固定为500
,错误消息也统一以固定的形式开头。
错误码枚举类
将应用中业务错误码我们定义在一个枚举中进行维护:
package com.xiaojuan.boot.common.enums; import ... @Getter @AllArgsConstructor public enum BusinessError { PARAM_INVALID("参数校验失败", 10001), RECORD_EXISTS("后台记录已存在", 10002), HAS_NO_ROLE("用户未授权,不能访问", 403), NO_LOGIN("请先登录再操作", 401); private final String label; private final Integer value; }
这里我们给出默认的说明,一般在抛出业务异常时我们可以指定更具体的错误,而不会使用这里默认的错误消息。
service层抛出异常调整
现在我们对service层抛出异常做下调整,一般我们只要传入错误信息来构造BusinessException
。有些时候我们还可以传入错误码来进一步区分这些错误。
实现自己的Assert工具
既然我们先前定义了自己的BusinessException
,并且在其中维护了业务错误码errCode
,就没必要用spring的Assert
工具了,因为它抛出来的是IllegalStateException
异常,最终被我们全局异常处理器以最大的Exception
的处理逻辑进行包装响应结果,自然“参数校验失败”的错误码类型就丢了,因此这里我们基于spring对Assert
的实现,我们包装为自己的Assert
:
package com.xiaojuan.boot.util; import ... public class Assert { public static void hasText(@Nullable String text, String message) { if (!StringUtils.hasText(text)) { throw new BusinessException(message, BusinessError.PARAM_INVALID.getValue()); } } }
controller层抛出异常
最后我们将原来预留todo
注释的地方改为抛出异常的形式:
最后,我们再基于test.http
对抛出异常的测试场景进行测试,看是否达到预期的http状态码和json结构,这个大家自行测试。
到此这篇关于基于spring boot实现一个全局异常处理器的文章就介绍到这了,更多相关spring boot全局异常处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!