SpringBoot項目統(tǒng)一枚舉轉(zhuǎn)換實踐過程
SpringBoot 項目統(tǒng)一枚舉轉(zhuǎn)換實踐
1 現(xiàn)有問題
目前的項目中,有些枚舉字段,在傳遞的時候,需要經(jīng)常對枚舉進(jìn)行對應(yīng)的轉(zhuǎn)換,有如下場景:
- 存儲進(jìn)數(shù)據(jù)庫的時候,需要存儲為 int;
- 查詢出來的時候,需要對該數(shù)值進(jìn)行轉(zhuǎn)換;
- 接收前端參數(shù)的時候,需要將數(shù)字轉(zhuǎn)換為我們系統(tǒng)的枚舉;
- 響應(yīng)的參數(shù)包含枚舉的時候,需要將枚舉轉(zhuǎn)換成 int;
- 發(fā)送或接收 MQ 消息時,又得對枚舉進(jìn)行轉(zhuǎn)換。
可以看到,我們在系統(tǒng)中需要做大量的枚舉轉(zhuǎn)換工作,那么是不是有什么方法對枚舉轉(zhuǎn)換進(jìn)行簡化呢?
2 數(shù)據(jù)庫轉(zhuǎn)換枚舉
我們這邊的系統(tǒng)使用的持久層框架是 Spring Data jpa,底層實現(xiàn)是 Hibernate,對于數(shù)據(jù)庫枚舉的轉(zhuǎn)化提供了 AttributeConverter 接口,可以實現(xiàn)枚舉和 int 自動轉(zhuǎn)換。
例如我們有一個枚舉類
@Getter @RequiredArgsConstructor public enum SexEnum { /** * 1 男 */ MALE(1, "男"), /** * 2 女 */ FEMALE(2, "女"), ; private final Integer value; }
那么,我們就可以實現(xiàn) AttributeConverter 接口,來實現(xiàn)枚舉的自動轉(zhuǎn)換。
@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); } }
但是這樣子每一個枚舉類又必須寫對應(yīng)的枚舉轉(zhuǎn)換的代碼,很麻煩,因此,接下來進(jìn)行優(yōu)化。
我們可以先定義一個父類的枚舉接口
public interface IEnum { Integer getValue(); }
然后優(yōu)化我們的枚舉轉(zhuǎn)換類
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); } }
然后枚舉類繼承我們的父類枚舉接口,再定義一個子類實現(xiàn),就可以實現(xiàn)枚舉的自動轉(zhuǎn)換
@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> { } }
這樣子的話,對于數(shù)據(jù)庫的查詢或者保存,就可以直接使用枚舉進(jìn)行操作,而不用再去手動轉(zhuǎn)換。
3 請求參數(shù)轉(zhuǎn)換枚舉
3.1 @Param 參數(shù)轉(zhuǎn)換成枚舉
對于請求參數(shù),我們分為兩種,先看第一種,攜帶在 URL 后面的參數(shù)。
spring 提供了一個 ConverterFactory 接口,我們通過實現(xiàn)他,便可以實現(xiàn)對枚舉的自動轉(zhuǎn)換,這里同樣要使用到我們的父類接口 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); } }
然后實現(xiàn) WebMvcConfigurer,添加該類型轉(zhuǎn)換器
@Configuration @RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { private final IEnumConverterFactory iEnumConverterFactory; @Override public void addFormatters(FormatterRegistry registry) { registry.addConverterFactory(iEnumConverterFactory); } }
然后我們便可以在在方法上使用枚舉,MVC 框架會自動幫我們把數(shù)值轉(zhuǎn)成對應(yīng)的枚舉。
3.2 @RequestBody 參數(shù)轉(zhuǎn)換成枚舉
使用 @RequestBody 接收請求參數(shù),底層其實是使用了 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(); // 如果是容器,則返回容器內(nèi)部枚舉類型 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 響應(yīng)參數(shù)轉(zhuǎn)換枚舉
我們返回響應(yīng)參數(shù)時,經(jīng)常需要把枚舉值轉(zhuǎn)成對應(yīng)的 int,因為Spring boot框架響應(yīng)參數(shù)的時候,采用的也是 Jackson 序列化,因此我們可以采用類似上面的做法,繼承 JsonSerializer 抽象類,實現(xiàn)自定義轉(zhuǎn)換。
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(); }
這樣子,我們在給前端返回枚舉的時候,就可以不用自己手動進(jìn)行轉(zhuǎn)換,讓框架自動幫我們進(jìn)行轉(zhuǎn)換。
5 消息參數(shù)轉(zhuǎn)換枚舉
其實通過我們上面的配置,已經(jīng)自定義了枚舉的序列化和反序列化,只需要在我們的消息接收和發(fā)送的時候,采用 Jackson 序列化的形式,就可以實現(xiàn)枚舉的自動轉(zhuǎn)換。
總結(jié)
通過以上的配置,基本上可以實現(xiàn)我們整個項目的枚舉統(tǒng)一,消除我們在項目內(nèi)頻繁需要對枚舉和數(shù)值互相轉(zhuǎn)換的操作。
如果是前端頁面有相關(guān)的枚舉下拉框選擇查詢,我們還可以根據(jù)頂層的枚舉接口,對所有枚舉進(jìn)行掃描并存儲到內(nèi)存中,然后提供枚舉查詢的接口給前端,這樣子的話,后續(xù)新增枚舉參數(shù)的話,可以直接就從接口獲取到更新的值。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
如何解決Spring in action @valid驗證不生效的問題
這篇文章主要介紹了如何解決Spring in action @valid驗證不生效的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06java面向?qū)ο笤O(shè)計原則之開閉原則示例解析
這篇文章主要介紹了java面向?qū)ο笤O(shè)計原則之開閉原則的示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2021-10-10Intellij Idea中進(jìn)行Mybatis逆向工程的實現(xiàn)
這篇文章主要介紹了Intellij Idea中進(jìn)行Mybatis逆向工程的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05java多次嵌套循環(huán)查詢數(shù)據(jù)庫導(dǎo)致代碼中數(shù)據(jù)處理慢的解決
這篇文章主要介紹了java多次嵌套循環(huán)查詢數(shù)據(jù)庫導(dǎo)致代碼中數(shù)據(jù)處理慢的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03深入理解Java中的volatile關(guān)鍵字(總結(jié)篇)
volatile這個關(guān)鍵字,不僅僅在Java語言中有,在很多語言中都有的,而且其用法和語義也都是不盡相同的。這篇文章主要介紹了Java中的volatile關(guān)鍵字,需要的朋友可以參考下2018-10-10