SpringBoot统一异常处理
概述
Spring在3.2版本增加了一个注解@ControllerAdvice
,可以与@ExceptionHandler
、@InitBinder
、@ModelAttribute
等注解注解配套使用。
简单的说,该注解可以把异常处理器应用到所有控制器,而不是单个控制器。借助该注解,我们可以实现:在独立的某个地方,比如单独一个类,定义一套对各种异常的处理机制,然后在类的签名加上注解@ControllerAdvice
,统一对 不同阶段的、不同异常 进行处理。这就是统一异常处理的原理。
对异常按阶段进行分类,大体可以分成:进入Controller前的异常 和 Service 层异常
目标就是消灭95%以上的 try catch 代码块,并以优雅的 Assert(断言) 方式来校验业务的异常情况,只关注业务逻辑,而不用花费大量精力写冗余的 try catch 代码块。
为什么要优雅的处理异常
在后端发生异常或者是请求出错时,前端通常显示如下
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Fri Jun 07 15:38:07 CST 2022
There was an unexpected error (type=Not Found, status=404).
No message available
对于用户来说非常不友好。
在 Controller 里提供接口,通常需要捕捉异常,进行异常处理。最简单的方法使用try/catch进行异常捕捉。
@Slf4j
@Api(value = "User Interfaces", tags = "User Interfaces")
@RestController
@RequestMapping("/user")
public class UserController {
/**
* http://localhost:8080/user/add .
*
* @param userParam user param
* @return user
*/
@ApiOperation("Add User")
@ApiImplicitParam(name = "userParam", type = "body", dataTypeClass = UserParam.class, required = true)
@PostMapping("add")
public ResponseEntity<String> add(@Valid @RequestBody UserParam userParam) {
// 每个接口充斥着大量的异常处理
try {
// do something
} catch(Exception e) {
return ResponseEntity.fail("error");
}
return ResponseEntity.ok("success");
}
}
当方法很多,每个都需要 try catch,代码会显得臃肿,写起来也比较麻烦。
这时就需要进行统一的异常处理。
全局异常统一处理
通过 Spring 的 AOP 特性就可以很方便的实现异常的统一处理:使用@ControllerAdvice、@RestControllerAdvice捕获运行时异常。
创建全局异常处理类GlobalExceptionHandler
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 基础异常
*/
@ExceptionHandler(BaseException.class)
public R baseException(BaseException e) {
log.warn(e.getMessage(), e);
return R.error(e.getMessage());
}
/**
* 业务异常
*/
@ExceptionHandler(BusinessException.class)
public R businessException(BusinessException e) {
log.warn(e.getMessage(), e);
if (StringUtils.isNull(e.getCode())) {
return R.error(e.getMessage());
}
return R.error(e.getCode(), e.getMessage());
}
}
创建基础异常处理类BaseException
public class BaseException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* 所属模块
*/
private String module;
/**
* 错误码
*/
private String code;
/**
* 错误码对应的参数
*/
private Object[] args;
/**
* 错误消息
*/
private String defaultMessage;
public BaseException(String module, String code, Object[] args, String defaultMessage) {
this.module = module;
this.code = code;
this.args = args;
this.defaultMessage = defaultMessage;
}
public BaseException(String module, String code, Object[] args) {
this(module, code, args, null);
}
public BaseException(String module, String defaultMessage) {
this(module, null, null, defaultMessage);
}
public BaseException(String code, Object[] args) {
this(null, code, args, null);
}
public BaseException(String defaultMessage) {
this(null, null, null, defaultMessage);
}
@Override
public String getMessage() {
String message = null;
if (!StringUtils.isEmpty(code)) {
message = MessageUtils.message(code, args);
}
if (message == null) {
message = defaultMessage;
}
return message;
}
public String getModule() {
return module;
}
public String getCode() {
return code;
}
public Object[] getArgs() {
return args;
}
public String getDefaultMessage() {
return defaultMessage;
}
}
创建业务异常处理类BusinessException
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
private Integer code;
private String message;
public BusinessException(String message) {
this.message = message;
}
public BusinessException(String message, Integer code) {
this.message = message;
this.code = code;
}
public BusinessException(String message, Throwable e) {
super(message, e);
this.message = message;
}
@Override
public String getMessage() {
return message;
}
public Integer getCode() {
return code;
}
}
业务实现类中抛出异常
@Service
public class UserServiceImpl {
public Object getBusinessException() {
throw new BusinessException(ExceptionEnum.IS_NOT_NULL.getCode(),
ExceptionEnum.IS_NOT_NULL.getMsg(), "参数");
}
}
具体的异常代码可以在ExceptionEnum
枚举类中自定义
/**
* 异常枚举类
*/
public enum ExceptionEnum {
// 400
BAD_REQUEST("400", "请求数据格式不正确!"),
UNAUTHORIZED("401", "登录凭证过期!"),
FORBIDDEN("403", "没有访问权限!"),
NOT_FOUND("404", "请求的资源找不到!"),
// 500
INTERNAL_SERVER_ERROR("500", "服务器内部错误!"),
SERVICE_UNAVAILABLE("503", "服务器正忙,请稍后再试!"),
// 未知异常
UNKNOWN("10000", "未知异常!"),
// 自定义
IS_NOT_NULL("10001","%s不能为空");
/**
* 错误码
*/
private String code;
/**
* 错误描述
*/
private String msg;
ExceptionEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}
public String getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
Controller接口中无需处理异常
@Api(tags = "用户")
@RestController
@RequestMapping("/user")
public class UserController{
@ApiOperation("Add User")
@ApiImplicitParam(name = "userParam", type = "body", dataTypeClass = UserParam.class, required = true)
@PostMapping("add")
public ResponseEntity<UserParam> add(@Valid @RequestBody UserParam userParam) {
return ResponseEntity.ok(userParam);
}
}