优雅对接前端-body to Json
前言
前文中已对get请求的query参数做了处理,使用的是ConverterFactory方式,当SpringMVC获取不到相应的Converter时,会寻找合适的ConverterFactory创建相应的Converter。
现在准备处理post请求的数据,本文只针对请求体为json格式的数据,使用的是SpringMVC下的jackson工具
枚举类的封装,在之前的文章能看到,本文就不做展示了
jackson-databind-2.13.3.jar
是SpringMVC自带的,非本人添加的依赖。
思路
再之前,我们已经处理好单个类型的json化了,本文的目的在于编写通用的序列化和反序列化类,实现通用化。
发现问题:Jackson未提供Factory相关内容,也未提供register方法。
考虑自行注册JsonDeserializer(失败)
尝试register
在开始处理问题之前,我测试了一下JsonDeserializer实例化规则:
在碰到一个类型所匹配的JsonDeserializer未实例化时,才会主动调用其无参构造器,而且只为一个类型创建一次JsonDeserializer。
但是我们需要在创建JsonDeserializer的时候,给定class给JsonDeserializer,使之初始化枚举类型数据。
所以我想到去接管实例化Deserializer的过程。
但jackson没有提供如Converter的注册方式。
尝试HttpMessageConverters
可以知道的是,SpringMVC是用HttpMessageConverters对请求体数据做转换的,而其中有一个MappingJackson2HttpMessageConverter,就是对json类型的body做处理的。
其中肯定有Deserializer的管理器。
可以看到当请求过来时,SpringMVC会找到想用的MessageConverter,对请求体做Convert,contentType 为json的MessageConverter用的就是jackson进行处理的。
可以看到这个上下文对象,其中就有Deserializer数据的缓存。
但接下来我却发现,这个上下文好像没有提供管理Deserializer的方法。。。
也有可能在别的地方,但是考虑到上下文都没提供此方法,如果在其他地方就不好找了。
所以只能转换思路。
ContextualDeserializer(成功)
过程
在翻看DeserializationContext的源码时,我重点找了找以JsonDeserializer为入参的方法,发现其中有一个方法是这样的
Deserializer 创建上下文?
再点进另一个参数 JavaType ,可以看到其中的内容
按照理解,当一个Deserializer是ContextualDeserializer实例时,就会使用JavaType,调用其createContextual方法,重新实例化 Deserializer,并覆盖原Deserializer。
先试一试:
发现确实能看到当前要封装的数据
同时,经过测试,发现第二次请求时,是不会再执行该方法的,所以可以放心使用。
编写ConstantEnumJsonDeserializer
既然可以使用该方法获取当前要转换的类型,也就表示可以在Deserializer初始化枚举类,这就解决了我们一开始的问题。
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.will.cn.constants.ConstantEnumIFace;
import java.io.IOException;
/**
* @author jeff
* @time 2023/5/1 22:19
* 第一个T,表示这个Deserializer只为实现ConstantEnumIFace的类做反序列化
* 第二个T,表示ConstantEnumIFace接口中的方法,返回的类型为T
* 第三个T,表示这个Deserializer只为T类型数据做反序列化
*/
public class ConstantEnumJsonDeserializer<T extends ConstantEnumIFace<T>> extends JsonDeserializer<T > implements ContextualDeserializer {
private T firstEnum;
@Override
public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
final String source = jsonParser.getText();
System.out.println("正在执行deserialize方法:"+source);
int i = Integer.parseInt(source);
return firstEnum.theEnum(i);
}
private void setFirstEnum(T firstEnum){
this.firstEnum=firstEnum;
}
@Override
public JsonDeserializer<T> createContextual(DeserializationContext deserializationContext, BeanProperty beanProperty) {
// 重新创建Deserializer
ConstantEnumJsonDeserializer<T> tUserStatusEnumDeserializer = new ConstantEnumJsonDeserializer<>();
// 设置枚举类型数据
JavaType type = beanProperty.getType();
Class<T> rawClass = (Class<T>)type.getRawClass();
System.out.println("正在创建JsonDeserializer,类型:"+rawClass.getName());
firstEnum = rawClass.getEnumConstants()[0];
tUserStatusEnumDeserializer.setFirstEnum(firstEnum);
return tUserStatusEnumDeserializer;
}
}
准备测试
然后再在之前抽取出来的ConstantEnumIFace接口上,加上此注解:
@JsonDeserialize(using = ConstantEnumJsonDeserializer.class)
表示碰到此类型时,使用ConstantEnumJsonDeserializer进行反序列化
而在真正实例化Deserializer时,则会传递真实数据类型。
这样就达成了为每一个枚举类型创建ConstantEnumJsonDeserializer的目的。
测试
可以看到首次请求时,创建相应的ConstantEnumJsonDeserializer,并执行了deserialize方法
返回数据的JsonSerializer
JsonSerializer处理起来就不麻烦了,因为抽取了获取value的方法,所以只使用父类引用的getValue方法获取实际常量就好。
代码
/**
* @author jeff
* @since 2023/5/1 11:46
*/
public class ConstantEnumJsonSerializer extends JsonSerializer<ConstantEnumIFace> {
@Override
public void serialize(ConstantEnumIFace constantEnum, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
System.out.println("正在执行serialize方法:"+constantEnum+",value:"+constantEnum.getValue());
jsonGenerator.writeNumber(constantEnum.getValue());
System.out.println("json写入完成");
}
}
总结
到这里,系统中使用枚举常量,对接前端数据的get,post请求数据的问题就处理完了,也把返回枚举常量的json的数据给解决了。
接下来就是mybatis的相关内容了。
发现问题
如果前端发过来的请求数据里,对应的枚举数据为空的话,就不会走json转换,也就意味着,当前端发的请求没有枚举项的数据,我们就无法在json转换时去给默认值,因为它不参与转换了