SpringBoot前后端参数优化

1.去除前端入参的两端空格
2.前后端枚举值互相转换
3.处理后端NULL值

全局序列化操作,方便前后端传参,优化后端接口的调用体验,减少纠纷。

包含内容

  • 前端传入的字符串类型参数,去除两端空格
  • 前端传入的枚举类型参数,转换成后端枚举对象,方便参数校验
  • 后端返回值为null时,对具体类型进行具体处理,如:list为null时,返回空数组

1~3都要用到Jackson2ObjectMapperBuilderCustomizer进行处理,首先定义一个JacksonConfig类,配置Bean

1
2
3
4
5
6
7
8
@Configuration
public class JacksonConfig {

@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return new Jackson2ObjectMapperBuilder().build();
}
}

1、前端传入的字符串类型参数,去除两端空格

使用@InitBinder注解,它用来在请求到达controller方法前进行数据绑定时进行操作,还可以做数据校验、参数类型转换等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@ControllerAdvice(basePackages = {"包路径"})
public class GlobalStringTrimConfig {
/**
* 去除get方式的参数空格
*
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
// 创建 String trim 编辑器
// 构造方法中 boolean 参数含义为如果是空白字符串,是否转换为null
// 即如果为true,那么 " " 会被转换为 null,否者为 ""
StringTrimmerEditor propertyEditor = new StringTrimmerEditor(false);
// 为 String 类对象注册编辑器
binder.registerCustomEditor(String.class, propertyEditor);
}
}

修改Jackson2ObjectMapperBuilderCustomizer,去除post参数空格,添加如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return bu -> {
//字符串去除空格
bu.deserializerByType(String.class, new StdScalarDeserializer<>(String.class) {
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {
return StringUtils.trimWhitespace(jsonParser.getValueAsString());
}
});
};
}

2、前端传入的枚举值,转换成后端枚举对象

创建一个公共的接口BaseEnum,将来所有需要转换的枚举类,都实现该接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 基础枚举接口,所有实现该枚举的都可以用作前后端传参和数据库存储
*/

public interface BaseEnum {

/**
* 根据枚举值或名称从枚举类型type中获取枚举对象
*/
static <T extends BaseEnum> T getEnum(Class<T> type, Object codeOrName) {
T[] enums = type.getEnumConstants();
for (T em : enums) {
if (em.getStrCode().equals(codeOrName.toString()) || em.name().equals(codeOrName.toString())) {
return em;
}
}
return null;
}

String getStrCode();

String name();
}

创建枚举类序列化方法

1
2
3
4
5
6
7
8
public class BaseEnumSerializer extends JsonSerializer<BaseEnum> {
@Override
public void serialize(BaseEnum value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeNumber(value.getStrCode());
// 增加一个字段,格式为【枚举类名称+Text】,存储枚举的name
gen.writeStringField(gen.getOutputContext().getCurrentName() + "Text", value.name());
}
}

创建整型和字符串类型的枚举转换器,用于数字或字符串类型转成枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class IntegerToEnumConverter<T extends BaseEnum> implements Converter<Integer, T> {
private Map<String, T> enumMap = new HashMap<>();

public IntegerToEnumConverter(Class<T> enumType) {
T[] enums = enumType.getEnumConstants();
for (T e : enums) {
enumMap.put(e.getStrCode(), e);
}
}

@Override
public T convert(Integer source) {
T t = enumMap.get(source);
if (Objects.isNull(t)) {
throw new IllegalArgumentException("无法匹配对应的枚举类型");
}
return t;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class StringToEnumConverter<T extends BaseEnum> implements Converter<String, T> {
private Map<String, T> enumMap = Maps.newHashMap();

public StringToEnumConverter(Class<T> enumType) {
T[] enums = enumType.getEnumConstants();
for (T e : enums) {
enumMap.put(e.getStrCode().toString(), e);
}
}

@Override
public T convert(String source) {
T t = enumMap.get(source);
if (Objects.isNull(t)) {
throw new IllegalArgumentException("无法匹配对应的枚举类型");
}
return t;
}
}

创建对应的工厂类,并将工厂类注册到mvc全局配置中

Integer类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class IntegerCodeToEnumConverterFactory implements ConverterFactory<Integer, BaseEnum> {
private static final Map<Class, Converter> CONVERTERS = new HashMap<>();

/**
* 获取一个从 Integer 转化为 T 的转换器,T 是一个泛型,有多个实现
*
* @param targetType 转换后的类型
* @return 返回一个转化器
*/
@Override
public <T extends BaseEnum> Converter<Integer, T> getConverter(Class<T> targetType) {
Converter<Integer, T> converter = CONVERTERS.get(targetType);
if (converter == null) {
converter = new IntegerToEnumConverter<>(targetType);
CONVERTERS.put(targetType, converter);
}
return converter;
}
}

String类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
public class StringCodeToEnumConverterFactory implements ConverterFactory<String, BaseEnum> {
private static final Map<Class, Converter> CONVERTERS = Maps.newHashMap();

@Override
public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> targetType) {
Converter<String, T> converter = CONVERTERS.get(targetType);
if (converter == null) {
converter = new StringToEnumConverter<>(targetType);
CONVERTERS.put(targetType, converter);
}
return converter;
}
}

注册到mvc配置

1
2
3
4
5
6
7
8
9
@Configuration
public class WebConfigurer implements WebMvcConfigurer {

@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new IntegerCodeToEnumConverterFactory());
registry.addConverterFactory(new StringCodeToEnumConverterFactory());
}
}

最后配置到Jackson2ObjectMapperBuilderCustomizer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return bu -> {
//枚举序列化
bu.serializerByType(BaseEnum.class, baseEnumSerializer);
bu.deserializerByType(BaseEnum.class, baseEnumDeserializer);
//字符串去除空格
bu.deserializerByType(String.class, new StdScalarDeserializer<>(String.class) {
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {
return StringUtils.trimWhitespace(jsonParser.getValueAsString());
}
});
};
}

3、后端返回值为null时,对具体类型进行相关处理

针对null值处理,有一个专门的序列化类NullValueSerializer进行操作,先创建一个自定义的空值序列化器,再注册到ObjectMapper中。

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
/**
* 空值序列化
*/
public class NullValueSerializer extends JsonSerializer<Object> {

@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
String fieldName = gen.getOutputContext().getCurrentName();
//反射获取字段
Field field = FieldUtils.getDeclaredField(gen.getCurrentValue().getClass(), fieldName, true);
if (Objects.nonNull(field)) {
//继承Collection类型为null时,返回空数组 [ ]
if (Collection.class.isAssignableFrom(field.getType())) {
gen.writeStartArray();
gen.writeEndArray();
return;
}
//....这里还可以针对Integer、String、Map等进行处理
if (Objects.equals(field.getType(), String.class)){
gen.writeString(""); //String为null,返回空字符串
return;
}
}
//其他Object默认返回null
gen.writeNull();
}
}

ObjectMapper类中注册NullValueSerializer序列化器

1
2
3
4
5
6
7
@Bean
@Primary
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.getSerializerProvider().setNullValueSerializer(new NullValueSerializer());
return objectMapper;
}

测试结果

创建Sex枚举类,实现BaseEnum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 注意枚举内容code和desc不要重复,参照BaseEnum的方法,重复会出现取值错误问题
*/
@Getter
public enum Sex implements BaseEnum {
MAN(1, "男"),
WOMAN(2, "女");

private Integer code;
private String desc;

Sex(Integer code, String desc) {
this.code = code;
this.desc = desc;
}

@Override
public String getStrCode() {
return code.toString();
}
}

创建user类,内容如下

1
2
3
4
5
6
7
8
9
@Data
public class User {

private String name; // 姓名
private Sex sex; // 性别
private Integer age; // 年龄
private String password; //密码
private List<String> deptList; //部门
}

添加测试接口

1
2
3
4
5
@PostMapping("/test")
public String test(@RequestBody User user, String password) {
user.setPassword(password);
return JSON.toJSONString(user);
}

调用接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 入参
{
"name": " 张三 ",
"sex": "1",
"password": "1123111 ",
"age": 50
}

//返回值
{
"name": "张三",
"sex": 1,
"sexText": "MAN",
"age": 50,
"password": "123121",
"deptList": []
}

image-20240326114155203