使用 Spring Validator 驗證地圖
1. 簡介
Spring 的驗證框架主要設計用於與 JavaBeans 協同工作,其中每個欄位都可以用驗證約束進行註解。 號
在本教學中,我們將探討如何使用 Spring 的Validator
介面來驗證Map<String, String>
。當處理不直接對應到預先定義 Java 物件的動態鍵值對時,此方法特別有用。
2. 理解問題 – Hibernate Validator 和 Maps
在實作自訂驗證器之前,很自然地會嘗試使用 Hibernate Validator 和 Spring 的內建驗證機制(如@
Valid and @Validated
)直接在映射結構上應用標準約束註解。不幸的是,這種方法並不像我們預期的那樣有效。
讓我們來看一個例子:
Map<@Length(min = 10) String, @NotBlank String> givenMap = new HashMap<>();
givenMap.put("tooShort", "");
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<Map<String, String>>> violations = validator.validate(givenMap);
Assertions.assertThat(violations).isNotEmpty(); // this will fall
儘管有註釋類型參數, violations
集仍將為空——不會檢測到任何約束違規。
2.1.為什麼會失敗?
Hibernate Validator,或一般的 Bean Validation,基於 JavaBeans 約定進行操作,這意味著它驗證可透過 getter 存取的物件屬性。由於映射不將鍵和值作為屬性公開,因此@Length
或@NotBlank
等約束註釋不能直接適用,並且在驗證期間會被忽略。
換句話說,從驗證器的角度來看,地圖是一個黑盒子——除非明確告知,否則它不知道如何自省其內容。
2.2.什麼時候起作用?
當映射是 JavaBean 內的屬性時,類型級約束註解可以起作用,如下所示:
public class WrappedMap {
private Map<@Length(min = 10) String, @NotBlank String> map;
// constructor, getters, setters...
}
這是因為 Hibernate Validator 支援容器元素約束。但是,鍵和值的驗證仍然有限且不一致,尤其是對於地圖而言。您可能需要明確啟用值提取器,即使這樣,也不能保證完全支援。
為了解決這個限制,我們可以透過實作 Spring 提供的Validator
介面來建立自訂驗證器。
3.項目配置
在實施我們的解決方案之前,我們需要為專案配置必要的依賴項。如果我們使用 Spring Boot,則所有內容都包含在一個啟動器中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>3.4.5</version>
</dependency>
但是,如果我們使用普通的Spring Framework ,則需要手動包含Jakarta Validation API及其Hibernate 實作:
<!-- Core Spring Framework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.2.6</version>
</dependency>
<!-- Validation related -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.2.Final</version>
</dependency>
這些依賴關係使我們能夠為我們的地圖結構實作和使用自訂驗證器。
4.實作自訂驗證器
專案設定完成後,我們就可以繼續實作自訂驗證器。我們將複製先前程式碼片段中的驗證規則,檢查鍵和值:
@Service
public class MapValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Map.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
Map<?, ?> rawMap = (Map<?, ?>) target;
for (Map.Entry<?, ?> entry : rawMap.entrySet()) {
Object rawKey = entry.getKey();
Object rawValue = entry.getValue();
if (!(rawKey instanceof String key) || !(rawValue instanceof String value)) {
errors.rejectValue("map[" + rawKey + "]", "map.entry.invalidType", "Map must contain only String keys and values");
continue;
}
// Key validation
if (key.length() < 10) {
errors.rejectValue("map[" + key + "]", "key.tooShort", "Key must be at least 10 characters long");
}
// Value validation
if (!StringUtils.hasText(value)) {
errors.rejectValue("map[" + key + "]", "value.blank", "Value must not be blank");
}
}
}
}
**此類別實作了Spring的Validator
接口**,需要兩個方法:
-
supports(Class<?> clazz)
– 確定此驗證器是否可以處理給定的類 -
validate(Object target, Errors errors)
——執行實際驗證並報告任何約束違規
請注意,我們明確檢查鍵和值的類型以確保類型安全性並避免在運行時出現ClassCastException
。
5. 呼叫驗證器
Spring 的驗證框架與服務類別順利集成,使我們能夠創建可重複使用的程式碼、注入它並在需要的地方使用我們的自訂驗證器。
我們現在可以在任何 Spring 管理的服務中註入並使用您的自訂驗證器:
@Service
public class MapService {
private final MapValidator mapValidator;
@Autowired
public MapService(MapValidator mapValidator) {
this.mapValidator = mapValidator;
}
public void process(Map<String, String> inputMap) {
// Wrap the map in a binding structure for validation
MapBindingResult errors = new MapBindingResult(inputMap, "inputMap");
// Run validation
mapValidator.validate(inputMap, errors);
// Handle validation errors
if (errors.hasErrors()) {
throw new IllegalArgumentException("Validation failed: " + errors.getAllErrors());
}
// Business logic goes here...
}
}
此範例展示如何使用建構函式註入來注入MapValidator
,並在執行核心業務邏輯之前呼叫它。將地圖包裝在MapBindingResult
中允許 Spring 一致地收集和建構驗證錯誤。
6. 結論
在 Spring 中驗證Map<String, String>
結構需要一種自訂方法,因為標準驗證機制預設不會檢查地圖內容。對 Bean 驗證的支援有限,可能無法以我們預期的方式運作。
透過實作Validator
介面並將其整合到您的服務層,我們可以完全控制每個鍵值對的驗證方式,從而使我們的應用程式更加健壯和靈活。此策略在處理動態輸入(如設定、使用者定義表單或第三方 JSON 結構)時特別有用。
與往常一樣,本文中使用的所有程式碼範例以及更多範例都可以在 GitHub 上找到。