本文代码示例:Github
场景
在工作中,经常需要定义一些枚举提供常量。例如,周一,周二等一些常用的属性。在前端通过Json格式传递此类参数到后端,需要将参数转换到枚举。
在Spring Web中默认使用的是 Jackson 来进行的 json 序列化化与反序列化。
Spring Mvc是通过 HttpMessageConverter
接口,进行参数到实体类的转换,提供了MappingJackson2HttpMessageConverter
实现类使用Jackson进行序列化反序列化。
Jackson支持枚举到序列化,不过默认使用的是使用枚举的ordinal
枚举定义的顺序。但是很多时候需要自定义枚举的值,使用下标无法满足需求。
Jackson 默认通用枚举序列化器EnumDeserializer
中Object deserialize(JsonParser p, DeserializationContext ctxt)
方法以枚举下标实现Json到枚举的反序列化。
需求
需求:实现一个自定义的枚举值反序列化到实体类。
枚举
public enum WeekDay implements BaseEnum {
Mon(1),
Tue(2),
Wed(3),
Thu(4),
Fri(5),
Sat(6),
Sun(7);
private final int code;
WeekDay(int code){
this.code = code;
}
@Override
public int getCode() {
return this.code;
}
}
请求实体类
@Data
public class WeekDayReq {
private WeekDay weekDay;
}
spring web接口,方法签名
@PostMapping("/put")
public Object req(@RequestBody WeekDayReq req)
请求Json
POST http://localhost:8081/demo1/put
Content-Type: application/json;charset=UTF-8
{
"weekDay":1
}
期望 json 中 weekDay的值,对应枚举的 Code值。eg: 1 对应枚举中的,WeekDay.Mon。
在使用默认的枚举处理器的情况下,返回的是 WeekDay.Tue。与期望值不符合。
现有方式
根据百度搜索到现有的实现方式。
-
使用
@JsonDeserialize
注解@JsonDeserialize
注解标注到指定的字段,其中参数using
指定使用自定义的序列化器.public class WeekDayReq { @JsonDeserialize(using = WeekDayDeserialize.class) private WeekDay weekDay; }
其中
using
参数需要指定一个JsonDeserializer
实现类,public class WeekDayDeserialize extends JsonDeserializer<WeekDay> { @Override public WeekDay deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { int value = p.getValueAsInt(); for (WeekDay d:WeekDay.values()){ if (value == d.getCode()){ return d; } } return null; } }
每次都需要使用注解,每个枚举都要写对应的实现类,可以实现,但不具备通用性。
源码分析
通过debug,查阅源码。
关于对应类型的JsonDeserializer
创建查找主要在com.fasterxml.jackson.databind.deser.DeserializerCache#_createDeserializer
方法中,会先从对应字段的注解配置中找是否有指定的JsonDeserializer
,没有的话则会从ObjectMapper配置中查找有无对应类型的JsonDeserializer
。找到后会new出实例对象,并存在缓存中。
查阅代码com.fasterxml.jackson.databind.deser.DeserializerCache#_createDeserializer
:
第一个箭头处,尝试从注解中查找是否有指定。没有的话就会进入第二个红箭头处,进入另个一个方法。
可以很清楚得看到,红箭头处。在判断是枚举类型后从factroy
中创建枚举的Deserializer。
进入方法createEnumDeserializer
可以看到。
会先 _findCustomEnumDeserializer
中查找,找不到的话会调用AbstractDeserializer#constructForNonPOJO()
方法创建一个默认的序列化器。
进入``_findCustomEnumDeserializer`方法
可以看到会尝试从所有Deserializers
中查找对应类型的JsonDeserializer
.
Deserializers
是一个接口,现在情况很清楚只要我们实现Deserializers
接口注入到Jackson中,就可以实现通用的枚举的反序列化器。
实现
实现Deserializers
接口
public class BaseEnumDeserializers implements Deserializers {
@Override
public JsonDeserializer<?> findEnumDeserializer(Class<?> type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException {
if (!type.isEnum() && !type.isAssignableFrom(BaseEnum.class)){
return null;
}
return new BaseEnumJsonDeserializer(type);
}
//省略部分接口
}
我新加入了一个接口BaseEnum
,自己的枚举都会继承这个接口实现获取自定义枚举code的方法,方便序列化以及反序列化。
public interface BaseEnum {
/**
* 获取枚举对应的值
* @return 枚举对应的值
*/
int getCode();
}
这里判断一些传入的type值是否为枚举并且继承了BaseEnum
,通过的话则会返回一个BaseEnumJsonDeserializer
对象,实现如下。
public class BaseEnumJsonDeserializer<T extends Enum<T> & BaseEnum> extends JsonDeserializer<T> {
private final Class<T> clz;
private final T[] clzEnums;
public BaseEnumJsonDeserializer(Class<T> t){
this.clz = t;
this.clzEnums = t.getEnumConstants();
}
@Override
public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
int value = p.getValueAsInt();
for (T clzEnum : clzEnums) {
if (value == clzEnum.getCode()) {
return clzEnum;
}
}
return null;
}
}
在BaseEnumJsonDeserializer
根据类型,获取列举。调用getCode
方法获取对应的枚举值进行判断。
将自己实现的BaseEnumDeserializers
注入到 ObjectMapper 中。
-
继承Module配置
继承
com.fasterxml.jackson.databind.Module
。public class EnumModule extends Module { @Override public String getModuleName() { return "BaseEnumModule"; } @Override public Version version() { return Version.unknownVersion(); } @Override public void setupModule(SetupContext context) { context.addDeserializers(new BaseEnumDeserializers()); } }
-
配置到ObjectMapper
objectMapper.registerModule(new EnumModule());
配置到spring mvc
获取所有的HttpMessageConverter
接口,判断是否为MappingJackson2HttpMessageConverter
对象,这个对象使用Jackson进行序列化的。
提示:HttpMessageConverter
使用的ObjectMapper 是在WebMvcConfigurationSupport#addDefaultHttpMessageConverters()方法中,根据Jackson2ObjectMapperBuilder实例化出来的,并没有进入容器进行管理。如有自己编写JsonUtils,注意需要自己配置EnumModule到ObjectMapper
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter){
ObjectMapper o = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
o.registerModule(new EnumModule());
}
}
}
}
上一篇: 这是第一篇
下一篇: Spring MVC Controller自定义参数和返回值解析