SpringBoot之使用枚舉參數(shù)案例詳解
接口開發(fā)過程中不免有表示類型的參數(shù),比如 0 表示未知,1 表示男,2 表示女。通常有兩種做法,一種是用數(shù)字表示,另一種是使用枚舉實(shí)現(xiàn)。
使用數(shù)字表示就是通過契約形式,約定每個(gè)數(shù)字表示的含義,接口接收到參數(shù),就按照約定對類型進(jìn)行判斷,接口維護(hù)成本比較大。
在 Spring 體系中,使用枚舉表示,是借助 Spring 的 Converter 機(jī)制,可以將數(shù)字或字符串對應(yīng)到枚舉的序號或者 name,然后將前端的輸入轉(zhuǎn)換為枚舉類型。
在場景不復(fù)雜的場景中,枚舉可以輕松勝任。
于是,迅速實(shí)現(xiàn)邏輯,準(zhǔn)備提測。這個(gè)時(shí)候需求變了,不允許選擇未知性別,只能選男或女,就沒有 0 值。這樣,因?yàn)槿≈凳菑?1 開始,而枚舉的序號是從 0 開始,就會(huì)產(chǎn)生沖突。
還有一些不太多的場景,就是前端不期望類型都是用數(shù)字,可能期望用一些有意義的字符串表示。但是按照前端規(guī)范,需要用小寫或者駝峰命名。但是后端的規(guī)范中,枚舉必須是大寫,又是沖突。
需求合不合理暫且不論,我們要保存對技術(shù)的探索精神。
確認(rèn)需求
首先確認(rèn)需求。我們期望定義一個(gè)枚舉類作為參數(shù),接口訪問的時(shí)候,可以是 int 類型的 id,id 取值不限于枚舉的序號;也可以是 String 類型的 code,code 取值不限于枚舉的 name。換句話說,這個(gè)枚舉有個(gè) id 和 code,隨意定義,只要接口傳過來匹配上,就能夠自動(dòng)轉(zhuǎn)成枚舉類型。
既然這樣,我們就規(guī)范下 id 和 code 取值。為了擴(kuò)展,定義三個(gè)接口:IdBaseEnum、CodeBaseEnum 以及 IdCodeBaseEnum。
public interface IdBaseEnum {
Integer getId();
}
public interface CodeBaseEnum {
String getCode();
}
public interface IdCodeBaseEnum extends IdBaseEnum, CodeBaseEnum {
}
接下來就該定義我們的主角了。
定義枚舉
前面定義了三個(gè)接口,分別是單獨(dú) id、單獨(dú) code,和有 id 和 code 的。這樣,我們就可以定義三種枚舉,分別對應(yīng)三個(gè)接口。三種方式類似,所以就不在文中重復(fù)列舉了。感興趣的可以關(guān)注公眾號「看山的小屋」回復(fù) spring 獲取源碼。
我們定義一個(gè)性別枚舉,枚舉包含 id 和 code 兩個(gè)屬性。
public enum GenderIdCodeEnum implements IdCodeBaseEnum {
MALE(1, "male"),
FEMALE(2, "female");
private final Integer id;
private final String code;
GenderIdCodeEnum(Integer id, String code) {
this.id = id;
this.code = code;
}
@Override
public String getCode() {
return code;
}
@Override
public Integer getId() {
return id;
}
}
這里需要注意一點(diǎn),id 和 code 不能重復(fù)。
- id 與 id、code 與 code 不能重復(fù),比如 MAIL 定義 id 是 1,F(xiàn)AMLE 就不能定義 id 是 1 了。
- id 與 code 之間也不能重復(fù),比如,MALE 定義 id 是 1001,F(xiàn)EMALE 定義 code 是 1001。
這是由于 Spring 在轉(zhuǎn)換參數(shù)的時(shí)候,將輸入?yún)?shù)全部視為 String 類型。雖然我們定義 id 和 code 類型不同,但是在匹配的時(shí)候,都是按照字符串匹配的。如果存在相同值,就會(huì)產(chǎn)生歧義。
Converter 和 ConverterFactory
根據(jù)規(guī)范,接下來定義一下 Converter 和 ConverterFactory。這些是 Spring 留給我們的擴(kuò)展口,按照規(guī)范定義即可。
Converter 類:
public class IdCodeToEnumConverter<T extends IdCodeBaseEnum> implements Converter<String, T> {
private final Map<String, T> idEnumMap = Maps.newHashMap();
private final Map<String, T> codeEnumMap = Maps.newHashMap();
public IdCodeToEnumConverter(Class<T> enumType) {
Arrays.stream(enumType.getEnumConstants())
.forEach(x -> {
idEnumMap.put(x.getId().toString(), x);
codeEnumMap.put(x.getCode(), x);
});
}
@Override
public T convert(String source) {
return Optional.of(source)
.map(codeEnumMap::get)
.orElseGet(() -> Optional.of(source)
.map(idEnumMap::get)
.orElseThrow(() -> new CodeBaseException(ErrorResponseEnum.PARAMS_ENUM_NOT_MATCH)));
}
}
ConverterFactory 類:
public class IdCodeToEnumConverterFactory implements ConverterFactory<String, IdCodeBaseEnum> {
@SuppressWarnings("rawtypes")
private static final Map<Class, Converter> CONVERTERS = Maps.newHashMap();
@Override
public <T extends IdCodeBaseEnum> Converter<String, T> getConverter(Class<T> targetType) {
//noinspection unchecked
Converter<String, T> converter = CONVERTERS.get(targetType);
if (converter == null) {
converter = new IdCodeToEnumConverter<>(targetType);
CONVERTERS.put(targetType, converter);
}
return converter;
}
}
這兩個(gè)就是轉(zhuǎn)換的核心了,我們只要將他們裝配到 Spring 的類型轉(zhuǎn)換器中,就能夠?qū)崿F(xiàn)枚舉類型的自動(dòng)轉(zhuǎn)化了。
加載配置
將我們定義的 Converter 和 ConverterFactory 注冊到 Spring 的類型轉(zhuǎn)換器中。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new IdCodeToEnumConverterFactory());
registry.addConverterFactory(new CodeToEnumConverterFactory());
registry.addConverterFactory(new IdToEnumConverterFactory());
}
}
至此,核心定義全部結(jié)束。
測試
寫一個(gè) Controller 作為測試入口:
@RestController
@RequestMapping("echo")
public class EchoController {
@GetMapping("gender-id-code")
public String genderIdCode(@RequestParam("gender") GenderIdCodeEnum gender) {
return gender.name();
}
}
準(zhǔn)備測試用例測試:
@SpringBootTest(classes = SpringEnumParamApplication.class)
@AutoConfigureMockMvc
class EchoControllerTest {
@Autowired
private MockMvc mockMvc;
@ParameterizedTest
@ValueSource(strings = {"MALE", "male", "1"})
void genderIdCode(String gender) throws Exception {
final String result = mockMvc.perform(
MockMvcRequestBuilders.get("/echo/gender-id-code")
.param("gender", gender)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn()
.getResponse()
.getContentAsString();
Assertions.assertEquals("MALE", result);
}
}
文末總結(jié)
實(shí)現(xiàn)枚舉參數(shù)并不難,只要按照 Spring 的擴(kuò)展規(guī)范實(shí)現(xiàn)即可。需要注意的是,注意枚舉類中唯一的 id 和 code。
本文是應(yīng)用,下篇說一下原理。以及 http body 形式請求的枚舉轉(zhuǎn)換邏輯。
推薦閱讀
- SpringBoot 實(shí)戰(zhàn):一招實(shí)現(xiàn)結(jié)果的優(yōu)雅響應(yīng)
- SpringBoot 實(shí)戰(zhàn):如何優(yōu)雅的處理異常
- SpringBoot 實(shí)戰(zhàn):通過 BeanPostProcessor 動(dòng)態(tài)注入 ID 生成器
- SpringBoot 實(shí)戰(zhàn):自定義 Filter 優(yōu)雅獲取請求參數(shù)和響應(yīng)結(jié)果
- SpringBoot 實(shí)戰(zhàn):優(yōu)雅的使用枚舉參數(shù)
- SpringBoot 實(shí)戰(zhàn):優(yōu)雅的使用枚舉參數(shù)(原理篇)
- SpringBoot 實(shí)戰(zhàn):在 RequestBody 中優(yōu)雅的使用枚舉參數(shù)
到此這篇關(guān)于SpringBoot之使用枚舉參數(shù)案例詳解的文章就介紹到這了,更多相關(guān)SpringBoot之使用枚舉參數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java基礎(chǔ)之finally語句與return語句詳解
這篇文章主要介紹了Java基礎(chǔ)之finally語句與return語句詳解,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04
基于SpringBoot整合oauth2實(shí)現(xiàn)token認(rèn)證
這篇文章主要介紹了基于SpringBoot整合oauth2實(shí)現(xiàn)token 認(rèn)證,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01
深入解析java HashMap實(shí)現(xiàn)原理
這篇文章主要介紹了深入解析java HashMap實(shí)現(xiàn)原理的相關(guān)資料,需要的朋友可以參考下2015-09-09
springboot使用maven實(shí)現(xiàn)多環(huán)境運(yùn)行和打包問題
這篇文章主要介紹了springboot使用maven實(shí)現(xiàn)多環(huán)境運(yùn)行和打包問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
springboot啟動(dòng)時(shí)如何指定spring.profiles.active
這篇文章主要介紹了springboot啟動(dòng)時(shí)如何指定spring.profiles.active問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04

