SpringBoot项目统一枚举转换实践过程
SpringBoot 项目统一枚举转换实践
1 现有问题
目前的项目中,有些枚举字段,在传递的时候,需要经常对枚举进行对应的转换,有如下场景:
- 存储进数据库的时候,需要存储为 int;
- 查询出来的时候,需要对该数值进行转换;
- 接收前端参数的时候,需要将数字转换为我们系统的枚举;
- 响应的参数包含枚举的时候,需要将枚举转换成 int;
- 发送或接收 MQ 消息时,又得对枚举进行转换。
可以看到,我们在系统中需要做大量的枚举转换工作,那么是不是有什么方法对枚举转换进行简化呢?
2 数据库转换枚举
我们这边的系统使用的持久层框架是 Spring Data jpa,底层实现是 Hibernate,对于数据库枚举的转化提供了 AttributeConverter 接口,可以实现枚举和 int 自动转换。
例如我们有一个枚举类
@Getter @RequiredArgsConstructor public enum SexEnum { /** * 1 男 */ MALE(1, "男"), /** * 2 女 */ FEMALE(2, "女"), ; private final Integer value; }
那么,我们就可以实现 AttributeConverter 接口,来实现枚举的自动转换。
@Converter(autoApply = true) public class SexEnumConverter implements AttributeConverter<SexEnum, Integer> { @Override public Integer convertToDatabaseColumn(SexEnum attribute) { return attribute == null ? null : attribute.getValue(); } @Override public SexEnum convertToEntityAttribute(Integer dbData) { return Arrays.stream(SexEnum.class) .filter(sexEnum -> sexEnum.getValue().equals(dbData)) .findFirst() .orElse(null); } }
但是这样子每一个枚举类又必须写对应的枚举转换的代码,很麻烦,因此,接下来进行优化。
我们可以先定义一个父类的枚举接口
public interface IEnum { Integer getValue(); }
然后优化我们的枚举转换类
public class EnumConverter<E extends IEnum> implements AttributeConverter<E, Integer> { private final Class<E> clazz; @SuppressWarnings("unchecked") public EnumConverter() { this.clazz = (Class<E>) (((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments())[0]; } @Override public Integer convertToDatabaseColumn(E attribute) { return Optional.ofNullable(attribute) .map(IEnum::getValue) .orElse(null); } @Override public E convertToEntityAttribute(Integer dbData) { return Arrays.stream(clazz.getEnumConstants()) .filter(iEnum -> iEnum.getValue().equals(dbData)) .findFirst() .orElse(null); } }
然后枚举类继承我们的父类枚举接口,再定义一个子类实现,就可以实现枚举的自动转换
@Getter @RequiredArgsConstructor public enum SexEnum implements IEnum { /** * 1 男 */ MALE(1, "男"), /** * 2 女 */ FEMALE(2, "女"), ; private final Integer value; @Converter(autoApply = true) public static class SexEnumConverter extends EnumConverter<SexEnum> { } }
这样子的话,对于数据库的查询或者保存,就可以直接使用枚举进行操作,而不用再去手动转换。
3 请求参数转换枚举
3.1 @Param 参数转换成枚举
对于请求参数,我们分为两种,先看第一种,携带在 URL 后面的参数。
spring 提供了一个 ConverterFactory 接口,我们通过实现他,便可以实现对枚举的自动转换,这里同样要使用到我们的父类接口 IEnum。
@Component public class IEnumConverterFactory implements ConverterFactory<String, IEnum> { @Override public <T extends IEnum> Converter<String, T> getConverter(Class<T> targetType) { return source -> Arrays.stream(targetType.getEnumConstants()) .filter(x -> x.getValue().toString().equals(source)) .findFirst() .orElse(null); } }
然后实现 WebMvcConfigurer,添加该类型转换器
@Configuration @RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { private final IEnumConverterFactory iEnumConverterFactory; @Override public void addFormatters(FormatterRegistry registry) { registry.addConverterFactory(iEnumConverterFactory); } }
然后我们便可以在在方法上使用枚举,MVC 框架会自动帮我们把数值转成对应的枚举。
3.2 @RequestBody 参数转换成枚举
使用 @RequestBody 接收请求参数,底层其实是使用了 Jackson 的反序列化功能。
我们可以继承 Jackson 的 JsonDeserializer 抽象类,自定义我们的反序列化策略。
public class IEnumDeserializer extends JsonDeserializer<IEnum> implements ContextualDeserializer { private Class<? extends IEnum> clazz; public IEnumDeserializer() { } public IEnumDeserializer(Class<? extends IEnum> clazz) { this.clazz = clazz; } @SneakyThrows @Override public IEnum deserialize(JsonParser jsonParser, DeserializationContext ctxt) { final String param = jsonParser.getText(); return Arrays.stream(clazz.getEnumConstants()) .filter(x -> x.getValue().toString().equals(param)) .findFirst() .orElse(null); } @SuppressWarnings({"unchecked"}) @Override public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { JavaType type = property.getType(); // 如果是容器,则返回容器内部枚举类型 while (type.isContainerType()) { type = type.getContentType(); } return new IEnumDeserializer((Class<? extends IEnum>) type.getRawClass()); } }
然后在我们的 IEnum 类上,添加 @JsonDeserialize 注解,指定反序列化。
@JsonDeserialize(using = IEnumDeserializer.class) public interface IEnum { Integer getValue(); }
这样子,就可以在 @RequestBody 注解的实体类里面使用枚举。
4 响应参数转换枚举
我们返回响应参数时,经常需要把枚举值转成对应的 int,因为Spring boot框架响应参数的时候,采用的也是 Jackson 序列化,因此我们可以采用类似上面的做法,继承 JsonSerializer 抽象类,实现自定义转换。
public class IEnumSerializer extends JsonSerializer<IEnum> { @Override public void serialize(IEnum value, JsonGenerator gen, SerializerProvider serializers) throws IOException { if (Objects.isNull(value)) { gen.writeNull(); return; } gen.writeNumber(value.getValue()); } }
然后在我们的 IEnum 类上,添加 @JsonDeserialize 注解,指定序列化。
@JsonSerialize(using = IEnumSerializer.class) @JsonDeserialize(using = IEnumDeserializer.class) public interface IEnum { Integer getValue(); }
这样子,我们在给前端返回枚举的时候,就可以不用自己手动进行转换,让框架自动帮我们进行转换。
5 消息参数转换枚举
其实通过我们上面的配置,已经自定义了枚举的序列化和反序列化,只需要在我们的消息接收和发送的时候,采用 Jackson 序列化的形式,就可以实现枚举的自动转换。
总结
通过以上的配置,基本上可以实现我们整个项目的枚举统一,消除我们在项目内频繁需要对枚举和数值互相转换的操作。
如果是前端页面有相关的枚举下拉框选择查询,我们还可以根据顶层的枚举接口,对所有枚举进行扫描并存储到内存中,然后提供枚举查询的接口给前端,这样子的话,后续新增枚举参数的话,可以直接就从接口获取到更新的值。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
相关文章
使用java基于pushlet和bootstrap实现的简单聊天室
这篇文章主要介绍了使用java基于pushlet和bootstrap实现的简单聊天室的相关资料,需要的朋友可以参考下2015-03-03Spring中的注解之@Override和@Autowired
看别人写的代码,经常会用到 @Override 和 @Autowired 这两个注解.这边总结一下这两个注解的作用,对正在学习java的小伙伴们有很好地帮助,需要的朋友可以参考下2021-05-05
最新评论