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复制代码@Override
public 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/…