Spring:如何使用枚舉參數
枚舉參數
接口開發過程中不免有表示類型的參數,比如 0 表示未知,1 表示男,2 表示女。通常有兩種做法,一種是用數字表示,另一種是使用枚舉實現。
使用數字表示就是通過契約形式,約定每個數字表示的含義,接口接收到參數,就按照約定對類型進行判斷,接口維護成本比較大。
在 Spring 體系中,使用枚舉表示,是借助 Spring 的 Converter 機制,可以將數字或字符串對應到枚舉的序號或者 name,然後將前端的輸入轉換為枚舉類型。
在場景不復雜的場景中,枚舉可以輕松勝任。
於是,迅速實現邏輯,準備提測。這個時候需求變瞭,不允許選擇未知性別,隻能選男或女,就沒有 0 值。這樣,因為取值是從 1 開始,而枚舉的序號是從 0 開始,就會產生沖突。
還有一些不太多的場景,就是前端不期望類型都是用數字,可能期望用一些有意義的字符串表示。但是按照前端規范,需要用小寫或者駝峰命名。但是後端的規范中,枚舉必須是大寫,又是沖突。
需求合不合理暫且不論,我們要保存對技術的探索精神。
確認需求
首先確認需求。我們期望定義一個枚舉類作為參數,接口訪問的時候,可以是 int 類型的 id,id 取值不限於枚舉的序號;也可以是 String 類型的 code,code 取值不限於枚舉的 name。換句話說,這個枚舉有個 id 和 code,隨意定義,隻要接口傳過來匹配上,就能夠自動轉成枚舉類型。
既然這樣,我們就規范下 id 和 code 取值。為瞭擴展,定義三個接口:IdBaseEnum、CodeBaseEnum 以及 IdCodeBaseEnum。
public interface IdBaseEnum { Integer getId(); } public interface CodeBaseEnum { String getCode(); } public interface IdCodeBaseEnum extends IdBaseEnum, CodeBaseEnum { }
接下來就該定義我們的主角瞭。
定義枚舉
前面定義瞭三個接口,分別是單獨 id、單獨 code,和有 id 和 code 的。這樣,我們就可以定義三種枚舉,分別對應三個接口。三種方式類似,所以就不在文中重復列舉瞭。感興趣的可以關註公眾號「看山的小屋」回復 spring 獲取源碼。
我們定義一個性別枚舉,枚舉包含 id 和 code 兩個屬性。
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; } }
這裡需要註意一點,id 和 code 不能重復。
1.id 與 id、code 與 code 不能重復,比如 MAIL 定義 id 是 1,FAMLE 就不能定義 id 是 1 瞭。
2.id 與 code 之間也不能重復,比如,MALE 定義 id 是 1001,FEMALE 定義 code 是 1001。
這是由於 Spring 在轉換參數的時候,將輸入參數全部視為 String 類型。雖然我們定義 id 和 code 類型不同,但是在匹配的時候,都是按照字符串匹配的。如果存在相同值,就會產生歧義。
Converter 和 ConverterFactory
根據規范,接下來定義一下 Converter 和 ConverterFactory。這些是 Spring 留給我們的擴展口,按照規范定義即可。
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; } }
這兩個就是轉換的核心瞭,我們隻要將他們裝配到 Spring 的類型轉換器中,就能夠實現枚舉類型的自動轉化瞭。
加載配置
將我們定義的 Converter 和 ConverterFactory 註冊到 Spring 的類型轉換器中。
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverterFactory(new IdCodeToEnumConverterFactory()); registry.addConverterFactory(new CodeToEnumConverterFactory()); registry.addConverterFactory(new IdToEnumConverterFactory()); } }
至此,核心定義全部結束。
測試
寫一個 Controller 作為測試入口:
@RestController @RequestMapping("echo") public class EchoController { @GetMapping("gender-id-code") public String genderIdCode(@RequestParam("gender") GenderIdCodeEnum gender) { return gender.name(); } }
準備測試用例測試:
@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); } }
總結
實現枚舉參數並不難,隻要按照 Spring 的擴展規范實現即可。需要註意的是,註意枚舉類中唯一的 id 和 code。
本文是應用,下篇說一下原理。以及 http body 形式請求的枚舉轉換邏輯。
本篇文章就到這裡瞭,希望能給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!
推薦閱讀:
- Springboot JPA 枚舉Enum類型存入到數據庫的操作
- SpringBoot實戰:Spring如何找到對應轉換器優雅使用枚舉參數
- Spring Boot中如何使用Convert接口實現類型轉換器
- SpringMVC RESTFul實體類創建及環境搭建
- Java靈活使用枚舉表示一組字符串的操作