Spring Boot统一结果响应和异常处理
在开发中,我们会经常使用公司或其他平台的各种接口,每个接口的功能各不相同,但是它们的响应结果基本上是一致的,都会包含code(响应码)、msg(错误信息)、data(真实数据)这三部分,有些公司会使用Map来返回这些信息,但是需要编写重复性的代码,不太优雅,而Spring为我们提供了能够简化代码编写的功能,下面就来尝试一下吧
项目准备
创建一个SpringBoot项目并引入
spring-boot-starter-web
依赖,分别创建controller和serviceUserController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class UserController {
private UserService userService;
public UserDO getById( Long id){
UserDO user = userService.getById(id);
return user;
}
}UserService.java
1
2
3
4
5
6
7
8
9
10
11
public class UserService {
private UserMapper userMapper;
public UserDO getById(Long id) {
return userMapper.getById(id);
}
}UserMapper.xml省略(可以直接硬编码返回,省略数据库配置)
测试controller返回结果
统一结果响应
创建
CommonResponse
和CommonResponseAdvice
CommonResponse.java
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
84
85
86
87
public class CommonResponse<T> {
/**
* 响应是否成功:
* true: 成功
* false: 失败
*/
private Boolean success;
/**
* 错误码
*/
private int errCode;
/**
* 错误信息
*/
private String errMsg;
/**
* 响应数据
*/
private T data;
public CommonResponse(Boolean success) {
this.success = success;
}
public CommonResponse(Boolean success, T data) {
this.success = success;
this.data = data;
}
public CommonResponse(Boolean success, int errCode, String errMsg) {
this.success = success;
this.errCode = errCode;
this.errMsg = errMsg;
}
/**
*
* @desc 响应成功无返回数据
* @author codecho
* @date 2021-12-05 15:22:07
*/
public static <T> CommonResponse<T> success() {
return new CommonResponse<>(true);
}
/**
*
* @desc 响应成功并返回数据
* @author codecho
* @date 2021-12-05 15:22:28
* @param data 响应数据
*/
public static <T> CommonResponse<T> success(T data) {
return new CommonResponse<>(true, data);
}
/**
*
* @desc 响应失败,无错误码,有错误信息
* @author codecho
* @date 2021-12-05 17:37:39
* @param errMsg 错误信息
*/
public static<T> CommonResponse<T> fail(String errMsg) {
return new CommonResponse<>(false, -1, errMsg);
}
/**
*
* @desc 响应失败,有错误码和错误信息
* @author codecho
* @date 2021-12-05 15:25:27
* @param errCode 错误码
* @param errMsg 错误信息
*/
public static <T> CommonResponse<T> fail(int errCode, String errMsg) {
return new CommonResponse<>(false, errCode, errMsg);
}
}CommonResponseAdvice.java
supports方法返回true表示响应结果需要进行重写
beforeBodyWrite方法对响应结果进行封装
1
2
3
4
5
6
7
8
9
10
11
12
13
public class CommonResponseAdvice implements ResponseBodyAdvice {
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
return CommonResponse.success(body);
}
}
重新运行程序,再次测试controller返回结果
统一异常处理
当程序出现异常时,controller返回的结果并不是我们希望获得的
创建ResponseStatusEnum、
CommonException
和CommonExceptionHandler
ResponseStatusEnum.java
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
26public enum ResponseStatusEnum {
/**
* 错误
*/
ERROR_500(500, "服务器未知错误"),
ERROR_400(400, "错误请求"),
USER_NOT_FOUND(233, "用户不存在");
private int code;
private String msg;
ResponseStatusEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}CommonException.java
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
26public class CommonException extends RuntimeException {
private static final long serialVersionUID = 6124960120588564481L;
private int code;
private ResponseStatusEnum statusEnum;
public CommonException(String message) {
super(message);
}
public CommonException(int code, String message) {
super(message);
this.code = code;
}
public CommonException(ResponseStatusEnum statusEnum) {
super(statusEnum.getMsg());
this.code = statusEnum.getCode();
this.statusEnum = statusEnum;
}
}CommonExceptionHandler.java
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
public class CommonExceptionHandler {
/**
* @desc 处理基础异常
* @author codecho
* @date 2022-11-23 15:59:48
* @param ex 基础异常
*/
public CommonResponse exceptionHandler(CommonException ex) {
// 输出日志
log.error("CommonException: {}", ex);
return CommonResponse.fail(ex.getCode(), ex.getMessage());
}
// ...处理自定义或常见的异常
/**
* @desc 处理其他异常
* @author codecho
* @date 2022-11-23 16:00:05
* @param ex 其他异常
*/
public CommonResponse exceptionHandler(Exception ex) {
// 输出日志
log.error("Exception: {}", ex);
return CommonResponse.fail(ex.getMessage());
}
}
修改
CommonResponseAdvice
CommonResponseAdvice.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CommonResponseAdvice implements ResponseBodyAdvice {
private static final String COMMON_RESPONSE = "CommonResponse";
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 配置统一异常处理或controller返回类型为CommonResponse,直接返回,不需要再次封装
if (Objects.nonNull(body) && COMMON_RESPONSE.equals(body.getClass().getSimpleName())) {
return body;
}
return CommonResponse.success(body);
}
}
修改UserController的getById方法
UserController.java
1
2
3
4
5
6
7
8
9
public UserDO getById( Long id){
UserDO user = userService.getById(id);
if (null == user) {
throw new CommonException(ResponseStatusEnum.USER_NOT_FOUND);
}
return user;
}
重新运行程序,测试controller返回结果
补充
有时候不是所有的接口都必须返回统一的格式,某些情况下我们想要自定义controller的返回,但是配置统一结果响应后,它是对注解
@RestControllerAdvice
的属性basePackages
的包和其子包生效的。如果想要在某些使其不生效,可以考虑使用自定义注解和重写ResponseBodyAdvice
接口的supports
方法来实现
创建自定义注解
@MyResponse
MyResponse.java
1
2
3
4
5
public MyResponse {
}
修改
CommonResponseAdvice
的supports
方法CommonResponseAdvice.java
1
2
3
4
5
6
7
8
9
public boolean supports(MethodParameter returnType, Class converterType) {
// 如果方法上有@MyResponse注解,返回false,不需要设置统一响应结果
if (returnType.hasMethodAnnotation(MyResponse.class)) {
return false;
}
return true;
}
在
UserController
中创建一个带有@MyResponse
注解的请求UserController.java
1
2
3
4
5
6
7
8
9
10
public UserDO mockUser() {
UserDO user = new UserDO();
user.setUsername("july");
user.setMobilePhone("18756989090");
user.setUserState(0);
return user;
}
重新运行程序,测试新的请求
可以看到,添加自定义注解后,统一结果响应未生效