知識庫 / Spring RSS 訂閱

使用 Spring 驗證器驗證地圖

Spring
HongKong
2
10:46 AM · Dec 06 ,2025

1. 介紹

Spring 的驗證框架主要設計用於與 JavaBeans 配合使用,其中每個字段都可以使用驗證約束進行標註。

在本教程中,我們將探索如何使用 Spring 的 <em>Validator</em> 接口驗證一個 <em>Map&lt;String, String&gt;</em>。這種方法特別適用於處理動態鍵值對,這些鍵值對不直接映射到預定義的 Java 對象。

2. 理解問題——Hibernate Validator 和 Maps

在實施自定義驗證器之前,自然會嘗試直接在 Maps 結構中使用 Hibernate Validator 和 Spring 內置的驗證機制(如 @Valid@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

儘管有標註的類型參數,但違規集將為空——不會檢測到任何約束違規。

2.1. 為什麼會失敗?

Hibernate Validator,或者 Bean Validation 總體上基於 JavaBeans 約定,這意味着它會驗證通過 getter 訪問的對象的屬性。由於映射(maps)不暴露鍵和值作為屬性,因此 約束註解(如@Length@NotBlank)在驗證過程中會被忽略,因為它們不適用

換句話説,從驗證器的角度來看,映射就像一個黑盒——它不知道如何內省其內容,除非明確地告訴它如何。

2.2. 何時有效?

類型級別約束註解可以在 JavaBean 中,當 map 作為屬性存在時生效,例如:

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>

這些依賴項使我們能夠實現和使用自定義的 Validator,用於我們的地圖結構。

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> 結構的有效性需要採用自定義方法,因為標準驗證機制默認不具備內省 map 內容的功能。 Bean 驗證的 支持有限,可能無法按預期工作。

通過實現 Validator 接口並將其集成到您的服務層中,我們完全掌控了每個鍵值對的驗證方式,從而使我們的應用程序更加健壯和靈活。 這種策略尤其適用於處理動態輸入,例如配置、用户自定義表單或第三方 JSON 結構。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.