前言:
做过业务项目开发的小伙伴都知道,开发总会避免不了各种各样的异常,为了不让每个业务繁琐的去处理异常,我们通常会去设置全局异常处理器来处理异常,本篇我们就分享如果使用全局异常处理器来处理异常。
全局异常处理器的两个注解
- @RestControllerAdvice:用于定义全局异常处理和全局数据绑定的类,它可以被应用于带有 @Controller 或 @RestController 注解的类上,用于统一处理这些类中抛出的异常,并对返回的数据进行统一处理,本篇主要讨论全局异常处理。
- @ExceptionHandler:注解用于统一处理控制层(Controller)往外抛的异常。
定义全局异常处理器
有了 这两个注解我们就可以定义全局异常处理器了,话不多说,直接代码演示如下:
java
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 表单绑定到 java bean 出错时抛出 BindException 异常
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BindException.class)
public <T> RetVo<T> processException(BindException e) {
log.error(e.getMessage(), e);
JSONObject msg = new JSONObject();
e.getAllErrors().forEach(error -> {
if (error instanceof FieldError) {
FieldError fieldError = (FieldError) error;
msg.put(fieldError.getField(),
fieldError.getDefaultMessage());
} else {
msg.put(error.getObjectName(),
error.getDefaultMessage());
}
});
return RetVo.fail(RetVoEnum.PARAM_ERROR, msg.toString());
}
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public <T> RetVo<T> handleBindGetException(MethodArgumentNotValidException e) {
log.error(e.getMessage(), e);
JSONObject msg = new JSONObject();
e.getBindingResult()
.getFieldErrors().forEach(error -> {
if (error instanceof FieldError) {
FieldError fieldError = (FieldError) error;
msg.put(fieldError.getField(),
fieldError.getDefaultMessage());
} else {
msg.put(error.getObjectName(),
error.getDefaultMessage());
}
});
return RetVo.fail(RetVoEnum.PARAM_ERROR, msg.toString());
}
/**
* 普通参数(非 java bean)校验出错时抛出 ConstraintViolationException 异常
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public <T> RetVo<T> processException(ConstraintViolationException e) {
log.error(e.getMessage(), e);
JSONObject msg = new JSONObject();
e.getConstraintViolations().forEach(constraintViolation -> {
String template = constraintViolation.getMessage();
String path = constraintViolation.getPropertyPath().toString();
msg.put(path, template);
});
return RetVo.fail(RetVoEnum.PARAM_ERROR, msg.toString());
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ValidationException.class)
public <T> RetVo<T> processException(ValidationException e) {
log.error(e.getMessage(), e);
return RetVo.fail(RetVoEnum.PARAM_ERROR, "参数校验失败");
}
/**
* NoHandlerFoundException
*/
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(NoHandlerFoundException.class)
public <T> RetVo<T> processException(NoHandlerFoundException e) {
log.error(e.getMessage(), e);
return RetVo.fail(RetVoEnum.RESOURCE_NOT_FOUND);
}
/**
* MissingServletRequestParameterException
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MissingServletRequestParameterException.class)
public <T> RetVo<T> processException(MissingServletRequestParameterException e) {
log.error(e.getMessage(), e);
return RetVo.fail(RetVoEnum.PARAM_IS_NULL);
}
/**
* MethodArgumentTypeMismatchException
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public <T> RetVo<T> processException(MethodArgumentTypeMismatchException e) {
log.error(e.getMessage(), e);
return RetVo.fail(RetVoEnum.PARAM_ERROR, "类型错误");
}
/**
* ServletException
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ServletException.class)
public <T> RetVo<T> processException(ServletException e) {
log.error(e.getMessage(), e);
return RetVo.fail(e.getMessage());
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public <T> RetVo<T> handleIllegalArgumentException(IllegalArgumentException e) {
log.error("非法参数异常,异常原因:{}", e.getMessage(), e);
return RetVo.fail(e.getMessage());
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(JsonProcessingException.class)
public <T> RetVo<T> handleJsonProcessingException(JsonProcessingException e) {
log.error("Json转换异常,异常原因:{}", e.getMessage(), e);
return RetVo.fail(e.getMessage());
}
/**
* HttpMessageNotReadableException
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(HttpMessageNotReadableException.class)
public <T> RetVo<T> processException(HttpMessageNotReadableException e) {
log.error(e.getMessage(), e);
String errorMessage = "请求体不可为空";
Throwable cause = e.getCause();
if (cause != null) {
errorMessage = convertMessage(cause);
}
return RetVo.fail(errorMessage);
}
/**
* TypeMismatchException
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(TypeMismatchException.class)
public <T> RetVo<T> processException(TypeMismatchException e) {
log.error(e.getMessage(), e);
return RetVo.fail(e.getMessage());
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BizException.class)
public <T> RetVo<T> handleBizException(BizException e) {
log.error("业务异常,异常原因:{}", e.getMessage(), e);
if (e.getResultCode() != null) {
return RetVo.fail(e.getResultCode());
}
return RetVo.fail(e.getMessage());
}
@ExceptionHandler(ZtenException.class)
public <T> RetVo<T> handleZtenException(ZtenException e) {
log.error("业务异常,异常原因:{},堆栈信息:{}", e.getMessage(), e);
return RetVo.fail(String.valueOf(e.getCode()), e.getMessage());
}
@ExceptionHandler(DataAccessException.class)
public <T> RetVo<T> handleSQLSyntaxErrorException(DataAccessException e) {
log.error("sql 错误,请确认sql是否正确");
e.printStackTrace();
return RetVo.fail("系统异常,请联系系统管理员处理");
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(Exception.class)
public <T> RetVo<T> handleException(Exception e) {
log.error("程序异常,异常详情:", e);
return RetVo.fail(e.getLocalizedMessage());
}
/**
* 传参类型错误时,用于消息转换
*
* @param throwable 异常
* @return 错误信息
*/
private String convertMessage(Throwable throwable) {
String error = throwable.toString();
String regulation = "\\[\"(.*?)\"]+";
Pattern pattern = Pattern.compile(regulation);
Matcher matcher = pattern.matcher(error);
String group = "";
if (matcher.find()) {
String matchString = matcher.group();
matchString = matchString
.replace("[", "")
.replace("]", "");
matchString = matchString.replaceAll("\\\"", "") + "字段类型错误";
group += matchString;
}
return group;
}
}
这个异常管理器对一般异常来说都好使,直到有一天业务反馈系统报错,直接打印了 SQL 语句,才意识到这个异常处理器的有巨大的问题,那针对 SQL 异常我们该如何处理呢?
全局处理 SQL 异常
SQL 语句直接暴露给用户,不仅仅体验感不好,还很容易就暴露数据库得表结构,为了避免 SQL 异常被用户感知,想到了加 SQL 异常拦截,想到了 SQLException 是所有 SQL 异常的子类,直接拦截 SQLException 即可,代码如下:
java
@ExceptionHandler(SQLException.class)
public <T> RetVo<T> handleSQLSyntaxErrorException(DataAccessException e) {
log.error("sql 错误,请确认sql是否正确");
e.printStackTrace();
return RetVo.fail("系统异常,请联系系统管理员处理");
}
没想到加了上述代码后,任然还是抛出了 SQL 语句,异常拦截无效,究竟是怎么回事呢?
是因为 @ExceptionHandler 注解只认我们注入的类名称,SQLException 类并没有注入,因此不生效,那怎么办?
Spring 为我们提供了 DataAccessException 类来处理 SQL 异常,我们只需要将 @ExceptionHandler 值换成 DataAccessException.class 即可,代码如下:
java
@ExceptionHandler(DataAccessException.class)
public <T> RetVo<T> handleSQLSyntaxErrorException(DataAccessException e) {
log.error("sql 错误,请确认sql是否正确");
e.printStackTrace();
return RetVo.fail("系统异常,请联系系统管理员处理");
}
测试结果:
powershell
{
"code": "30002",
"message": "系统异常,请联系系统管理员处理",
"data": null
}
一个全局异常处理器看似简单,但是可能也存在漏洞,比如上面介绍的 SQL 异常的处理,SQL 异常的处理有一定的特殊性,因此分享给大家,希望帮助到有需要的小伙伴。
欢迎提出建议及对错误的地方指出纠正。