Spring应用enum处理

原文地址:https://www.yangbajing.me/2019/04/17/spring应用enum处理/

在Spring应用开发中,Java枚举(enum)默认都是使用字符串进行序列化/反序列化的。都通常我们都想将其序列化/反序列化为int值。

MyBatis

MyBatis-plus提供了插件用于自定义enum的序列化/反序列化,非常方便。只需要在application.properties配置文件中指定默认的枚举处理器即可,配置如下:

1
mybatis-plus.configuration.default-enum-type-handler: com.baomidou.mybatisplus.extension.handlers.EnumTypeHandler

枚举类需要实现IEnum接口或将字段标记@EnumValue注解,这样MyBatis-plus在遇到相关枚举类型时就会通过指定的配置来序列/反序列化。

Jackson

序列化

Jackson的配置要相对复杂一点,Jackson对序列化提供了默认的支持,在要使用的字段上加JsonValue注解即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public enum UserStatusEnum implements IEnum<Integer> {
DISABLE(0, "禁用"),
NORMAL(1, "正常"),
PLAIN(999, "普通");

@JsonValue
private Integer value;

private String name;

UserStatusEnum(Integer value, String name) {
this.value = value;
this.name = name;
}

public String getName() {
return name;
}

public Integer getValue() {
return value;
}
}

反序列化

从int反序列化到enum,需要自定义IEnumDeserializer反序列化器,代码如下:

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
public class EnumDeserializers extends Deserializers.Base {
@Override
public JsonDeserializer<?> findEnumDeserializer(Class<?> type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException {
if (IEnum.class.isAssignableFrom(type)) {
return new IEnumDeserializer(EnumResolver.constructUnsafe(type, config.getAnnotationIntrospector()));
}
return super.findEnumDeserializer(type, config, beanDesc);
}
}

public class IEnumDeserializer extends StdScalarDeserializer<IEnum<Integer>> implements ContextualDeserializer {
private final IEnum<Integer>[] enums;
private final EnumResolver enumResolver;

public IEnumDeserializer(EnumResolver byNameResolver) {
super(byNameResolver.getEnumClass());
this.enumResolver = byNameResolver;
this.enums = (IEnum<Integer>[]) enumResolver.getRawEnums();
}

@Override
public IEnum<Integer> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
int value = p.getIntValue();

return Arrays.stream(this.enums).filter(e -> e.getValue() == value).findFirst()
.orElseThrow(() -> new JsonParseException(p, "枚举需要为整数类型"));
}

@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
return this;
}

}

并实现Jackson Module:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyModule extends Module {
@Override
public String getModuleName() {
return "MyModule";
}

@Override
public Version version() {
return Version.unknownVersion();
}

@Override
public void setupModule(SetupContext context) {
context.addDeserializers(new EnumDeserializers());
}
}

反序列化器和Jackson Module定义好后就需要把它加入Jackson里了,有两种方式:

手动注册:

获取objectMapper,将MyModule注册到Jackson。

objectMapper.registerModule(new MyModule());

自动注册:

通过Java自带的ServiceLoader服务提供商加载机制来自动注册模块到Jackson。在resources资源目录创建服务配置文件,文件路径如下:

1
2
3
resources
META-INF.services
com.fasterxml.jackson.databind.Module

服务配置文件内容:

1
com.fasterxml.jackson.module.yangbajing.MyModule

自定义Spring Boot Jackson

反序列化器、Jackson模块都写好了,我们需要自定义Spring Boot来启动Jackson的模块自动注册功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

@EnableWebFlux
@Configuration
public class WebConfiguration implements WebFluxConfigurer {

@Autowired
private ObjectMapper objectMapper;

@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
ServerCodecConfigurer.ServerDefaultCodecs defaultCodecs = configurer.defaultCodecs();
defaultCodecs.jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper));
defaultCodecs.jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper));
}

@Bean
@Order(-1)
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
return builder.createXmlMapper(false).build().findAndRegisterModules();
}
}

findAndRegisterModules()方法将通过ServiceLoader机制从classpath路径中找到所有的Module并注册到Jackson。

void configureHttpMessageCodecs(ServerCodecConfigurer configurer)函数中自定义ServerDefaultCodecs,使用新的ObjectMapper来注册jackson2JsonEncoderjackson2JsonDecoder

小结

Java提供了丰富而完善的enum机制,但大部化序列化/反序列化工具都使用文字来对其进行序列化/反序列化。而通常我们都会枚举序列化/反序列化为int。

Spring还是一个优秀的框架的,从公司/组织层面选择它不会错。

可以在 https://github.com/yangbajing/spring-reactive-sample 找到示例代码。