🚩 需求描述

封装统一的接口返回对象,使得前端能够获取格式规整的数据。

📦 封装对象

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
public class RestResponse<T> implements Serializable {
private String code;
private T data;
private boolean success;
private String message;

/**
* 获取统一返回类型
*
* @param code 返回码
* @param data 返回数据
* @param success 返回结果
* @param message 提示信息
* @param <T> 数据类型
* @return {@code RestResponse} 统一返回类型
*/
public static <T> RestResponse<T> of(String code, T data, boolean success, String message) {
return new RestResponse<>(code, data, success, message);
}

/**
* 返回操作成功的统一类型
* <p> {@link RestResponse#success} = true </p>
*
* @param code 返回码
* @param data 返回数据
* @param message 提示信息
* @param <T> 数据类型
* @return {@code RestResponse} 统一返回类型
*/
public static <T> RestResponse<T> success(String code, T data, String message) {
return of(code, data, true, message);
}

/**
* 返回操作成功的统一类型
* <p> {@link RestResponse#success} = true </p>
* <p> {@link RestResponse#code} = 0 </p>
*
* @param data 返回数据
* @param message 提示信息
* @param <T> 数据类型
* @return {@code RestResponse} 统一返回类型
*/
public static <T> RestResponse<T> success(T data, String message) {
return success("0", data, message);
}

/**
* 返回操作成功的统一类型
* <p> {@link RestResponse#success} = true </p>
* <p> {@link RestResponse#code} = 0 </p>
* <p> {@link RestResponse#message} = 响应成功 </p>
*
* @param data 返回数据
* @param <T> 数据类型
* @return {@code RestResponse} 统一返回类型
*/
public static <T> RestResponse<T> success(T data) {
return success(data, "响应成功");
}

/**
* 返回操作失败的统一类型
* <p> {@link RestResponse#success} = false </p>
*
* @param code 返回码
* @param data 返回数据
* @param message 提示信息
* @param <T> 数据类型
* @return {@code RestResponse} 统一返回类型
*/
public static <T> RestResponse<T> error(String code, T data, String message) {
return of(code, data, false, message);
}

/**
* 返回操作成功的统一类型
* <p> {@link RestResponse#success} = false </p>
* <p> {@link RestResponse#data} = null </p>
*
* @param message 提示信息
* @param <T> 数据类型
* @return {@code RestResponse} 统一返回类型
*/
public static <T> RestResponse<T> error(String code, String message) {
return error(code, null, message);
}

/**
* 返回操作成功的统一类型
* <p> {@link RestResponse#success} = true </p>
* <p> {@link RestResponse#code} = -1 </p>
* <p> {@link RestResponse#data} = null </p>
*
* @param <T> 数据类型
* @return {@code RestResponse} 统一返回类型
*/
public static <T> RestResponse<T> error(String message) {
return error("-1", message);
}
}

🏗 实现基础

spring 中提供了  ResponseBodyAdvice 接口,用于 http 请求在写入响应流之前的处理,为统一返回对象提供了相对便捷的实现方法。

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
public interface ResponseBodyAdvice<T> {

/**
* Whether this component supports the given controller method return type
* and the selected {@code HttpMessageConverter} type.
* @param returnType the return type
* @param converterType the selected converter type
* @return {@code true} if {@link #beforeBodyWrite} should be invoked;
* {@code false} otherwise
*/
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);

/**
* Invoked after an {@code HttpMessageConverter} is selected and just before
* its write method is invoked.
* @param body the body to be written
* @param returnType the return type of the controller method
* @param selectedContentType the content type selected through content negotiation
* @param selectedConverterType the converter type selected to write to the response
* @param request the current request
* @param response the current response
* @return the body that was passed in or a modified (possibly new) instance
*/
@Nullable
T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response);

}
方法名 作用
supports() 通过判断响应返回值类型及消息转换器的类型决定是否启用该组件
beforeBodyWrite() 当组件启用时,该方法返回值将作为最终的响应数据写入到流

🔜 流程

  1. 创建一个实现 ResponseBodyAdvice 接口的类;
  2. supports() 中跳过已经封装过(即类型已经是 RestResponse)的返回值,避免多次封装;
  3. beforeBodyWrite() 方法中进行统一返回类型的封装;

📄 实现代码

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
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
// 当返回值类型已经时包装类时跳过
return !returnType.getParameterType().equals(RestResponse.class);
}

@Override
public Object beforeBodyWrite(Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
// 1、处理无返回值(即 void)类型的方法
if (returnType.getParameterType().equals(Void.class)) {
return RestResponse.success(new Object());
}
// 2、Spring 默认使用 StringHttpMessageConverter 处理 String 类型的返回值
// 一旦被包装成统一对象后,不能被转换成字符串,而产生类型转换异常 - 见下方注①
if (body instanceof String) {
// 2.1、用于重置字符串的响应类型为 application/json
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
// 2.2 将统一对象转换成 json 字符串
return JSON.toJSONString(RestResponse.success(body));
}
// 3、其他所有情况
return RestResponse.success(body);
}

☝ 注意事项

注 ①:需要对 String 类型的返回值进行额外的处理,因为 Spring 通过返回值类型来决定消息转换器的选择,所以 String 类型的返回值会默认使用 StringHttpMessageConverter 处理器,但是在 beforeBodyWrite()  处理后,其类型被修改为  RestResponse,因此无法进行转换,出现如下报错:

1
com.example.bean.RestResponse cannot be cast to java.lang.String

为了能够处理 String 类型返回值,此处将其封装成统一类型之后再次使用 Json 转换器将其转换成 Json 字符串,最终将向响应流写入如下格式的数据:

1
{"code":"0","data":"收到消息: 123","message":"响应成功","success":true}

使用 Postman 表现形式为不支持格式化的字符串,因为其 Content-Type:application/json,所以此处通过 response.getHeaders().setContentType(MediaType.APPLICATION_JSON); 强制将响应类型标记为 Json 格式。

1
2
3
4
5
6
{
"code": "0",
"data": "收到消息: 123",
"message": "响应成功",
"success": true
}

💪 功能增强

任何类都可以实现 ResponseBodyAdvice 接口,但是为了使其产生预期效果,需要了解其作用范围,默认仅为该类本身,也就是说,若由 Controller 实现,则仅对该控制器生效。因此该接口通常配合 @ControllerAdvice 注解使用。

🍂 其他

📩 消息转换器

Spring 向响应流写入数据是通过消息转换器(实现 HttpMessageConverter 接口)来处理的,其默认实现了一系列转换器,链式的处理返回值,直到能够正常写入响应流 。对于复杂类型默认使用MappingJackson2HttpMessageConverter 处理器,将复杂类型转换为 Json 格式的字符串,对于字符串类型默认使用 StringHttpMessageConverter 处理器。

ControllerAdvice 注解


Java bean web spring

评论