SpringBoot教程(九) | SpringBoot统一异常处理
异常大家应该都很清楚,我们的项目总是不可避免的出现异常,那么应该如何优雅的进行异常处理使我们需要关注的一个问题,合理的异常封装既可以方便前端的处理,也能够简化后端的开发。
一般情况下,我们应该在我们的项目中,根据不同的异常场景,定义不同的异常类型,然后不同的异常类型,返回不同的状态码,然后和前端约定好,根据不同的状态码,做不同的展现。
SpringBoot中为我们提供一个统一的异常处理类,也是利用了AOP的思想,我们可以向外抛出各种类型的异常,然后在这个统一的处理类中,针对每一种不同类型的异常,做不同的数据封装,返回给前端。
代码编写:主要就是通过一个 @ControolerAdvice注解,实现对所有请求的拦截,很像AOP。
(注意,下面的代码仅供展示,如果大家直接粘贴,可能需要引入一些三方jar包才行)
java复制代码@RestControllerAdvice
@Order(1)
public class GlobalExceptionHandler {private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);public GlobalExceptionHandler() {}@ExceptionHandler({ParamException.class, MethodArgumentNotValidException.class, ConstraintViolationException.class, BindException.class, HttpMessageNotReadableException.class, MissingServletRequestPartException.class, MissingServletRequestParameterException.class, MultipartException.class})public Result<?> paramsExceptionHandler(HttpServletRequest request, Exception e) {String msg;if (e instanceof MethodArgumentNotValidException) {MethodArgumentNotValidException ex = (MethodArgumentNotValidException)e;msg = this.handlerErrors(ex.getBindingResult());} else if (e instanceof BindException) {BindException ex = (BindException)e;msg = this.handlerErrors(ex.getBindingResult());} else if (e instanceof ConstraintViolationException) {ConstraintViolationException ex = (ConstraintViolationException)e;Optional<ConstraintViolation<?>> first = ex.getConstraintViolations().stream().findFirst();msg = (String)first.map(ConstraintViolation::getMessage).get();} else {msg = e.getMessage();}Result<?> result = Result.error(ResultCode.PARAM_ERROR.getCode(), msg);return this.printLogAndReturn(request, result, e);}private String handlerErrors(BindingResult bindingResult) {List<FieldError> errors = bindingResult.getFieldErrors();FieldError error = (FieldError)errors.get(0);return error.getDefaultMessage();}@ExceptionHandler({BizException.class})public Result<?> bizExceptionHandler(HttpServletRequest request, BizException e) {Result<?> result = Result.error(e.getCode() == null ? ResultCode.BIZ_ERROR.getCode() : e.getCode(), e.getMessage());return this.printLogAndReturn(request, result, e);}@ExceptionHandler({HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotSupportedException.class})public Result<?> httpRequestMethodNotSupportedExceptionHandler(HttpServletRequest request, Exception e) {Result<?> result = Result.error(ResultCode.REQ_MODE_NOT_SUPPORTED);return this.printLogAndReturn(request, result, e);}@ExceptionHandler({JSONException.class})public Result<?> jsonExceptionHandler(HttpServletRequest request, Exception e) {Result<?> result = Result.error(ResultCode.JSON_FORMAT_ERROR);return this.printLogAndReturn(request, result, e);}@ExceptionHandler({DataAccessException.class})public Result<?> sqlExceptionHandler(HttpServletRequest request, Exception e) {Result<?> result = Result.error(ResultCode.SQL_ERROR);return this.printLogAndReturn(request, result, e);}@ExceptionHandler({Exception.class})@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Result<?> exceptionHandler(HttpServletRequest request, Exception e) {Result<?> result = Result.error(ResultCode.SYS_ERROR);return this.printLogAndReturn(request, result, e);}private Result<?> printLogAndReturn(HttpServletRequest request, Result<?> result, Exception e) {String requestUrl = request.getRequestURL().toString() + (StringUtil.isEmpty(request.getQueryString()) ? "" : "?" + request.getQueryString());log.error("<-异常返回-> 请求接口:{} | 异常时间:{} | 异常结果:{}", new Object[]{requestUrl, System.currentTimeMillis(), JSON.toJSONString(result)});log.error("<--异常堆栈信息-->");log.error(Throwables.getStackTraceAsString(e));return result;}
}
@ExceptionHandler 标识对哪种异常进行拦截。这里可以有我们自己定义的异常。当我们在业务代码中有一些异常处理的时候,我们可以根据具体的业务场景,将其抛出为我们自己定义的异常,然后在统一的异常处理类中,根据不同的异常类型,返回我们统一封装的结果。
大家可以看看上面的代码,对于所有的错误都封装成了Result对象,并且打印了异常的信息。
好的,接下来我们来写一个案例。首先把前面的统一结果的封装加入到项目中
- 在exception 自定义一个业务异常类
java复制代码public class BizException extends RuntimeException {private Integer code;public BizException() {}public BizException(String message) {super(message);}public BizException(Integer code, String message) {super(message);this.code = code;}public BizException(ResultCode resultCode) {super(resultCode.getMsg());this.code = resultCode.getCode();}public BizException(String message, Throwable cause) {super(message, cause);}public BizException(int code, String message, Throwable cause) {super(message, cause);this.code = code;}public BizException(ResultCode resultCode, Throwable cause) {super(resultCode.getMsg(), cause);this.code = resultCode.getCode();}public Integer getCode() {return this.code;}public void setCode(Integer code) {this.code = code;}
}
- 然后在把刚才的异常处理类也加到exception包下。
java复制代码@RestControllerAdvice
@Order(1)
public class GlobalExceptionHandler {private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);public GlobalExceptionHandler() {}private String handlerErrors(BindingResult bindingResult) {List<FieldError> errors = bindingResult.getFieldErrors();FieldError error = (FieldError)errors.get(0);return error.getDefaultMessage();}@ExceptionHandler({BizException.class})public Result<?> bizExceptionHandler(HttpServletRequest request, BizException e) {Result<?> result = Result.error(e.getCode() == null ? ResultCode.BIZ_ERROR.getCode() : e.getCode(), e.getMessage());return this.printLogAndReturn(request, result, e);}@ExceptionHandler({HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotSupportedException.class})public Result<?> httpRequestMethodNotSupportedExceptionHandler(HttpServletRequest request, Exception e) {Result<?> result = Result.error(ResultCode.REQ_MODE_NOT_SUPPORTED);return this.printLogAndReturn(request, result, e);}@ExceptionHandler({Exception.class})@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Result<?> exceptionHandler(HttpServletRequest request, Exception e) {Result<?> result = Result.error(ResultCode.SYS_ERROR);return this.printLogAndReturn(request, result, e);}private Result<?> printLogAndReturn(HttpServletRequest request, Result<?> result, Exception e) {ObjectMapper mapper = new ObjectMapper();String requestUrl = request.getRequestURL().toString() + (!StringUtils.hasLength(request.getQueryString()) ? "" : "?" + request.getQueryString());try {log.error("<-异常返回-> 请求接口:{} | 异常时间:{} | 异常结果:{}", new Object[]{requestUrl, System.currentTimeMillis(), mapper.writeValueAsString(result)});} catch (JsonProcessingException jsonProcessingException) {jsonProcessingException.printStackTrace();}log.error("<--异常堆栈信息-->");StringWriter stringWriter = new StringWriter();e.printStackTrace(new PrintWriter(stringWriter));log.error(stringWriter.toString());return result;}
}
接下来我们就可以在程序中使用。
- 开发一个Controller进行测试,直接抛出异常
java复制代码@RestController
public class ThirdExceptionController {@GetMapping("exception")public User second(){System.out.println(1);throw new BizException(ResultCode.BIZ_ERROR.getCode(), "用户名密码错误");}}
记得传token,因为有我们的拦截器。
异常成功按照我们想要的格式返回了。当然我们可以在抛出异常的时候,自己的定义我们的code和message, 其实还可以和Assert联合使用,让代码更加的优雅。
我们同时修改一下我们之前的拦截器,之前的拦截器在拦截token的时候,如果没传token就直接返回false这种方式不是很友好,因为在浏览器上看到就是一个空白页。http请求不会继续执行,我们可以在这里不返会false,而是直接封装一个我们自己定义的异常。
java复制代码@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 核心业务逻辑,判断是否登录等String token = request.getHeader("token");// 正常token是的登录后签发的,前端携带过来if(!StringUtils.hasLength(token)){throw new BizException(9001, "token不能为空");}return true;}
浏览器验证效果:
好了关于异常的处理我们就讲解到这里,希望对你有帮助。
另: 配套项目代码已托管中gitCode: gitcode.net/lsqingfeng/…