
該圖片由Sinousxl在Pixabay上發布
你好,我是看山,
介面開發程序中不免有表示型別的引數,比如 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 不能重復,
- id 與 id、code 與 code 不能重復,比如 MAIL 定義 id 是 1,FAMLE 就不能定義 id 是 1 了,
- 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 形式請求的列舉轉換邏輯,
關注公眾號「看山的小屋」回復 spring 獲取原始碼,原始碼中完整定義了三種列舉形式,
你好,我是看山,公眾號:看山的小屋,10 年老猿,開源貢獻者,游于碼界,戲享人生,
個人主頁:https://www.howardliu.cn
個人博文:Spring 實戰:優雅的使用列舉引數
CSDN 主頁:https://kanshan.blog.csdn.net/
CSDN 博文:Spring 實戰:優雅的使用列舉引數

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/293354.html
標籤:java
上一篇:Java基礎知識之泛型簡單介紹
