SpringBoot項目統(tǒng)一枚舉轉(zhuǎn)換實踐過程
SpringBoot 項目統(tǒng)一枚舉轉(zhuǎn)換實踐
1 現(xiàn)有問題
目前的項目中,有些枚舉字段,在傳遞的時候,需要經(jīng)常對枚舉進行對應的轉(zhuǎn)換,有如下場景:
- 存儲進數(shù)據(jù)庫的時候,需要存儲為 int;
- 查詢出來的時候,需要對該數(shù)值進行轉(zhuǎn)換;
- 接收前端參數(shù)的時候,需要將數(shù)字轉(zhuǎn)換為我們系統(tǒng)的枚舉;
- 響應的參數(shù)包含枚舉的時候,需要將枚舉轉(zhuǎn)換成 int;
- 發(fā)送或接收 MQ 消息時,又得對枚舉進行轉(zhuǎn)換。
可以看到,我們在系統(tǒng)中需要做大量的枚舉轉(zhuǎn)換工作,那么是不是有什么方法對枚舉轉(zhuǎ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);
}
}但是這樣子每一個枚舉類又必須寫對應的枚舉轉(zhuǎ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ù)庫的查詢或者保存,就可以直接使用枚舉進行操作,而不用再去手動轉(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)成對應的枚舉。
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 響應參數(shù)轉(zhuǎn)換枚舉
我們返回響應參數(shù)時,經(jīng)常需要把枚舉值轉(zhuǎn)成對應的 int,因為Spring boot框架響應參數(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();
}這樣子,我們在給前端返回枚舉的時候,就可以不用自己手動進行轉(zhuǎ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)換的操作。
如果是前端頁面有相關的枚舉下拉框選擇查詢,我們還可以根據(jù)頂層的枚舉接口,對所有枚舉進行掃描并存儲到內(nèi)存中,然后提供枚舉查詢的接口給前端,這樣子的話,后續(xù)新增枚舉參數(shù)的話,可以直接就從接口獲取到更新的值。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
如何使用RequestHeaders添加自定義參數(shù)
這篇文章主要介紹了使用RequestHeaders添加自定義參數(shù)方式,具有很好的參考價值,希望對大家有所幫助。2022-02-02
實戰(zhàn)分布式醫(yī)療掛號系統(tǒng)登錄接口整合阿里云短信詳情
這篇文章主要為大家介紹了實戰(zhàn)分布式醫(yī)療掛號系統(tǒng)登錄接口整合阿里云短信詳情,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪<BR>2022-04-04
Spring解決循環(huán)依賴的方法及三級緩存機制實踐案例
Spring通過三級緩存解決單例Bean循環(huán)依賴,但無法處理構(gòu)造器、prototype作用域及@Async場景,建議使用setter注入、@Lazy注解和架構(gòu)優(yōu)化,遵循設計原則避免依賴問題,本文介紹Spring如何解決循環(huán)依賴:深入理解三級緩存機制,感興趣的朋友一起看看吧2025-09-09

