SpringBoot解决方案之 统一异常处理

SpringBoot接口如何对异常进行统一封装,并统一返回呢?

实现案例

@ControllerAdvice异常统一处理

结合 统一接口封装 的思想,在异常处理中也将统一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Slf4j
@Component
@RestControllerAdvice
public class UnifiedExceptionHandler {
/**
* 未定义异常
*/
@ExceptionHandler(Exception.class)
public R exception(Exception e){
log.error(e.getMessage(),e);
return R.error();
}

@ExceptionHandler(BadSqlGrammarException.class)
public R badSqlGrammarException(BadSqlGrammarException e){
log.error(e.getMessage(),e);
return R.setResult(ResponseEnum.BAD_SQL_GRAMMAR_ERROR);
}

@ExceptionHandler(BusinessException.class)
public R businessException(BusinessException e){
log.error(e.getMessage(),e);
return R.error().message(e.getMessage()).code(e.getCode());
}

/**
* Controller上一层相关异常
*/
@ExceptionHandler({
NoHandlerFoundException.class,
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
MethodArgumentNotValidException.class,
HttpMediaTypeNotAcceptableException.class,
ServletRequestBindingException.class,
ConversionNotSupportedException.class,
MissingServletRequestPartException.class,
AsyncRequestTimeoutException.class
})
public R handleServletException(Exception e) {
log.error(e.getMessage(), e);
//SERVLET_ERROR(-102, "servlet请求异常"),
return R.error().message(ResponseEnum.SERVLET_ERROR.getMessage()).code(ResponseEnum.SERVLET_ERROR.getCode());
}
}

@BusinessException

BusinessException 异常是我们自定义的运行时异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@Data
@NoArgsConstructor
public class BusinessException extends RuntimeException {

//状态码
private Integer code;

//错误消息
private String message;

/**
* @param message 错误消息
*/
public BusinessException(String message) {
this.message = message;
}

/**
* @param message 错误消息
* @param code 错误码
*/
public BusinessException(String message, Integer code) {
this.message = message;
this.code = code;
}

/**
* @param message 错误消息
* @param code 错误码
* @param cause 原始异常对象
*/
public BusinessException(String message, Integer code, Throwable cause) {
super(cause);
this.message = message;
this.code = code;
}

/**
* @param resultCodeEnum 接收枚举类型
*/
public BusinessException(ResponseEnum resultCodeEnum) {
this.message = resultCodeEnum.getMessage();
this.code = resultCodeEnum.getCode();
}

/**
* @param resultCodeEnum 接收枚举类型
* @param cause 原始异常对象
*/
public BusinessException(ResponseEnum resultCodeEnum, Throwable cause) {
super(cause);
this.message = resultCodeEnum.getMessage();
this.code = resultCodeEnum.getCode();
}
}

这里也与 前文的 统一接口封装 灵活运用,可以传入 ResponseEnum 枚举 快捷定义异常消息

除此之外,还可以仿照 spring 的 Assert 类,让我们更方便的 抛出自定义异常,并且还可以使代码看起来更整洁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
@Slf4j
public abstract class Assert {

/**
* 断言对象不为空
* 如果对象obj为空,则抛出异常
* @param obj 待判断对象
*/
public static void notNull(Object obj, ResponseEnum responseEnum) {
if (obj == null) {
log.info("obj is null...............");
throw new BusinessException(responseEnum);
}
}


/**
* 断言对象为空
* 如果对象obj不为空,则抛出异常
* @param object
* @param responseEnum
*/
public static void isNull(Object object, ResponseEnum responseEnum) {
if (object != null) {
log.info("obj is not null......");
throw new BusinessException(responseEnum);
}
}

/**
* 断言表达式为真
* 如果不为真,则抛出异常
*
* @param expression 是否成功
*/
public static void isTrue(boolean expression, ResponseEnum responseEnum) {
if (!expression) {
log.info("fail...............");
throw new BusinessException(responseEnum);
}
}

/**
* 断言两个对象不相等
* 如果相等,则抛出异常
* @param m1
* @param m2
* @param responseEnum
*/
public static void notEquals(Object m1, Object m2, ResponseEnum responseEnum) {
if (m1.equals(m2)) {
log.info("equals...............");
throw new BusinessException(responseEnum);
}
}

/**
* 断言两个对象相等
* 如果不相等,则抛出异常
* @param m1
* @param m2
* @param responseEnum
*/
public static void equals(Object m1, Object m2, ResponseEnum responseEnum) {
if (!m1.equals(m2)) {
log.info("not equals...............");
throw new BusinessException(responseEnum);
}
}

/**
* 断言参数不为空
* 如果为空,则抛出异常
* @param s
* @param responseEnum
*/
public static void notEmpty(String s, ResponseEnum responseEnum) {
if (StringUtils.isEmpty(s)) {
log.info("is empty...............");
throw new BusinessException(responseEnum);
}
}
}

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@ApiOperation("会员注册")
@PostMapping("/register")
public R register(@RequestBody RegisterVO registerVO){
String mobile = registerVO.getMobile();
String password = registerVO.getPassword();
String code = registerVO.getCode();

//后台表单验证,防止恶意攻击
Assert.notEmpty(mobile, ResponseEnum.MOBILE_NULL_ERROR);
Assert.isTrue(RegexValidateUtils.checkCellphone(mobile),ResponseEnum.MOBILE_ERROR);
Assert.notEmpty(password, ResponseEnum.PASSWORD_NULL_ERROR);
Assert.notEmpty(code, ResponseEnum.CODE_NULL_ERROR);
//获取验证码
String codeGen = (String) redisTemplate.opsForValue().get("jkr:sms:code:" + mobile);
//验证验证码
Assert.equals(code,codeGen,ResponseEnum.CODE_ERROR);

userInfoService.register(registerVO);

return R.ok().message("注册成功");
}