Spring Boot 中的枚舉映射
一、概述
在本教程中,我們將探討在 Spring Boot 中實現不區分大小寫的枚舉映射的不同方法。
首先,我們將了解枚舉在 Spring 中是如何默認映射的。然後,我們將學習如何應對區分大小寫的挑戰。
2. Spring默認枚舉映射
Spring 在處理請求參數時依賴於幾個內置的轉換器來處理字符串轉換。
通常,當我們將枚舉作為請求參數傳遞時,它會在後台使用StringToEnumConverterFactory
將傳遞的String
轉換為enum
.
按照設計,此轉換器調用Enum.valueOf(Class, String)
這意味著給定的字符串必須與聲明的枚舉常量之一完全匹配。
例如,讓我們考慮Level
枚舉:
public enum Level {
LOW, MEDIUM, HIGH
}
接下來,讓我們創建一個接受枚舉作為參數的處理程序方法:
@RestController
@RequestMapping("enummapping")
public class EnumMappingController {
@GetMapping("/get")
public String getByLevel(@RequestParam(required = false) Level level){
return level.name();
}
}
讓我們使用 CURL 向http://localhost:8080/enummapping/get?level=MEDIUM
發送請求:
curl http://localhost:8080/enummapping/get?level=MEDIUM
處理程序方法發回MEDIUM
,枚舉常量MEDIUM
的名稱。
現在,讓我們傳遞medium
而不是MEDIUM
,看看會發生什麼:
curl http://localhost:8080/enummapping/get?level=medium
{"timestamp":"2022-11-18T18:41:11.440+00:00","status":400,"error":"Bad Request","path":"/enummapping/get"}
正如我們所看到的,請求被認為是無效的並且應用程序失敗並出現錯誤:
Failed to convert value of type 'java.lang.String' to required type 'com.baeldung.enummapping.enums.Level';
nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.web.bind.annotation.RequestParam com.baeldung.enummapping.enums.Level] for value 'medium';
...
查看堆棧跟踪,我們可以看到 Spring 拋出了ConversionFailedException.
它不將medium
識別為枚舉常量。
3.不區分大小寫的枚舉映射
Spring 提供了幾種方便的方法來解決映射枚舉時區分大小寫的問題。
讓我們仔細看看每種方法。
3.1.使用ApplicationConversionService
ApplicationConversionService
類帶有一組已配置的轉換器和格式化程序。
在這些開箱即用的轉換器中,我們找到了StringToEnumIgnoringCaseConverterFactory.
顧名思義,它以不區分大小寫的方式將字符串轉換為枚舉。
首先,我們需要添加和配置ApplicationConversionService
:
@Configuration
public class EnumMappingConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
ApplicationConversionService.configure(registry);
}
}
此類使用適用於大多數 Spring Boot 應用程序的現成轉換器配置FormatterRegistry
。
現在,讓我們使用測試用例確認一切都按預期工作:
@RunWith(SpringRunner.class)
@WebMvcTest(EnumMappingController.class)
public class EnumMappingIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
public void whenPassingLowerCaseEnumConstant_thenConvert() throws Exception {
mockMvc.perform(get("/enummapping/get?level=medium"))
.andExpect(status().isOk())
.andExpect(content().string(Level.MEDIUM.name()));
}
}
正如我們所見,作為參數傳遞的medium
值已成功轉換為MEDIUM
。
3.2.使用自定義轉換器
另一種解決方案是使用自定義轉換器。在這裡,我們將使用 Apache Commons Lang 3 庫。
首先,我們需要添加它的依賴:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
這裡的基本思想是創建一個轉換字符串的轉換器 Level
常量到真實Level
常量的表示:
public class StringToLevelConverter implements Converter<String, Level> {
@Override
public Level convert(String source) {
if (StringUtils.isBlank(source)) {
return null;
}
return EnumUtils.getEnum(Level.class, source.toUpperCase());
}
}
從技術角度來看,自定義轉換器是一個實現Converter<S,T>
接口的簡單類。
如我們所見,我們將String
對象轉換為大寫。然後,我們使用 Apache Commons Lang 3 庫中的EnumUtils
實用程序類從大寫字符串中獲取Level
常量。
現在,讓我們添加最後一塊缺失的拼圖。我們需要告訴 Spring 我們新的自定義轉換器。為此,我們將使用與之前相同的FormatterRegistry
。它提供了addConverter()
方法來註冊自定義轉換器:
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToLevelConverter());
}
就是這樣。我們的StringToLevelConverter
現在在ConversionService
中可用。
現在,我們可以像使用任何其他轉換器一樣使用它:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = EnumMappingMainApplication.class)
public class StringToLevelConverterIntegrationTest {
@Autowired
ConversionService conversionService;
@Test
public void whenConvertStringToLevelEnumUsingCustomConverter_thenSuccess() {
assertThat(conversionService.convert("low", Level.class)).isEqualTo(Level.LOW);
}
}
如上所示,測試用例確認“low”
值已轉換為Level.LOW
。
3.3.使用自定義屬性編輯器
Spring 在後台使用多個內置屬性編輯器來管理String
值和 Java 對象之間的轉換。
同樣,我們可以創建一個自定義屬性編輯器來將String
對象映射到Level
常量。
例如,讓我們將自定義編輯器命名為LevelEditor
:
public class LevelEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) {
if (StringUtils.isBlank(text)) {
setValue(null);
} else {
setValue(EnumUtils.getEnum(Level.class, text.toUpperCase()));
}
}
}
如我們所見,我們需要擴展PropertyEditorSupport
類並覆蓋setAsText()
方法。
覆蓋etAsText()
的想法是將給定字符串的大寫版本轉換為Level
枚舉。
值得注意的是, PropertyEditorSupport
也提供了getAsText()
。它在將 Java 對象序列化為字符串時調用。所以,我們不需要在這裡重寫它。
我們需要註冊LevelEditor
,因為 Spring 不會自動檢測自定義屬性編輯器。為此,我們需要在 Spring 控制器中創建一個用@InitBinder
註釋的方法:
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
dataBinder.registerCustomEditor(Level.class, new LevelEditor());
}
現在我們將所有部分放在一起,讓我們確認我們的自定義屬性編輯器LevelEditor
使用測試用例工作:
public class LevelEditorIntegrationTest {
@Test
public void whenConvertStringToLevelEnumUsingCustomPropertyEditor_thenSuccess() {
LevelEditor levelEditor = new LevelEditor();
levelEditor.setAsText("lOw");
assertThat(levelEditor.getValue()).isEqualTo(Level.LOW);
}
}
這裡要提到的另一件重要的事情是EnumUtils.getEnum()
在找到時返回枚舉,否則返回null
。
所以,為了避免NullPointerException
,我們需要稍微改變我們的處理方法:
public String getByLevel(@RequestParam(required = false) Level level) {
if (level != null) {
return level.name();
}
return "undefined";
}
現在,讓我們添加一個簡單的測試用例來測試它:
@Test
public void whenPassingUnknownEnumConstant_thenReturnUndefined() throws Exception {
mockMvc.perform(get("/enummapping/get?level=unknown"))
.andExpect(status().isOk())
.andExpect(content().string("undefined"));
}
4。結論
在本文中,我們學習了在 Spring 中實現不區分大小寫的枚舉映射的多種方法。
在此過程中,我們研究了一些使用內置和自定義轉換器來完成此操作的方法。然後,我們了解瞭如何使用自定義屬性編輯器實現相同的目標。
與往常一樣,本文中使用的代碼可以在 GitHub 上找到。