实现
定义一个实现 ResponseBodyAdvice 接口的类作为 ControllerAdviceBean 在将 Controller 方法返回值写入响应体前设置字段的默认值就可以了。
这里我们使用User 类做测试,该类定义如下。
@Data
@Accessors(chain = true)
public class User {
private String username;
private String password;
private Address address;
private List<String> interests;
private Map<String, Object> extra;
}
@Data
public class Address {
private String province;
private String city;
}
注意我们的 User 类字段包括普通的 String 类型,自定义的类型、List 类型 及 Map 类型。
接口返回的通用格式使用 Result 类表示。
@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> ok(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("OK");
result.setData(data);
return result;
}
}
我们定义一个测试接口如下。
@RestController
public class UserController {
@GetMapping("/login")
public Result<User> login() {
User user = new User();
Result<User> result = Result.ok(user);
return result;
}
}
为了将 login 方法返回的 Result<User>
写入到响应体前设置默认字段值,我们可以定义 ResponseBodyAdvice 实现如下。
import org.springframework.beans.BeanUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ControllerAdvice
public class ResponseBodyDefaultValueAdvice implements ResponseBodyAdvice<Result<?>> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 仅处理 Result 类型
return returnType.getParameterType() == Result.class;
}
@Override
public Result<?> beforeBodyWrite(Result<?> result, MethodParameter methodParameter, MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
Object data = result.getData();
if (data == null) {
data = BeanUtils.instantiateClass(ReflectionUtils.findField(result.getClass(), "data").getType());
}
setDefault(data);
return result;
}
private void setDefault(Object obj) {
ReflectionUtils.doWithFields(obj.getClass(), field -> {
ReflectionUtils.makeAccessible(field);
Object val = ReflectionUtils.getField(field, obj);
if (val != null) {
return;
}
// 字段默认值
Object defaultValue = null;
if (field.getType() == String.class) {
defaultValue = "";
} else if (field.getType() == List.class) {
defaultValue = Collections.emptyList();
} else if (field.getType() == Map.class) {
defaultValue = Collections.emptyMap();
} else if (field.getType().getPackage().getName().startsWith("com.weiah")) {
defaultValue = BeanUtils.instantiateClass(field.getType());
setDefault(defaultValue);
}
// 设置字段默认值
ReflectionUtils.setField(field, obj, defaultValue);
});
}
}
这里我们只处理 Result 类型的 Controller 方法返回值,返回值写入响应前,如果字段值为 null 则进行处理,并将 String 类型的值设置为空字符串、List 类型的值设置为空列表、Map 类型的值设置为空 Map 值,以及递归处理了自定义的类型。
测试接口返回内容如下。
{
"code": 200,
"message": "OK",
"data": {
"username": "",
"password": "",
"address": {
"province": "",
"city": ""
},
"interests": [],
"extra": {}
}
}
String、List、Map 以及自定义的 Address 类型都进行了处理,前端同学再也不会吐槽后端接口返回 null 了。