首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Spring MVC Validator 實戰:從 JSR-303 內置注解到跨字段自定義校驗

     編輯:Java 綜合教學

Spring MVC Validator 實戰:從 JSR-303 內置注解到跨字段自定義校驗

表單數據校驗是系統健壯性的第一道防線。將校驗邏輯與業務代碼解耦,利用 Spring MVC + Hibernate Validator(JSR-303 實現)可實現聲明式校驗,大幅簡化 Controller 代碼。

常見校驗分類

  • 非空/非空字符串:@NotNull、@NotEmpty、@NotBlank
  • 數值範圍:@Min、@Max、@DecimalMin、@DecimalMax、@Digits
  • 字符串格式:@Pattern(正則)、@Email、@Length、@Size
  • 時間約束:@Past、@Future
  • 布爾固定值:@AssertTrue、@AssertFalse

基礎用法示例

@NotNull(message = "基礎數量不能為空")
@Min(value = 0, message = "基礎數量不合法")
private Integer baseQty;

嵌套對象校驗

屬性為自定義對象/集合時,添加 @Valid 觸發遞歸校驗:

@Valid
@NotNull(message = "價格策略內容不能為空")
private List<ProductPricePolicyItem> policyItems;

進階:跨字段自定義校驗注解

場景:密碼/確認密碼一致、最小數量 ≤ 最大數量等多字段組合校驗。

1. 定義注解 @CrossFieldMatch

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = CrossFieldMatchValidator.class)
@Documented
public @interface CrossFieldMatch {
    String message() default "{constraints.crossfieldmatch}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    String first();      // 第一字段名
    String second();     // 第二字段名
    CrossFieldOperator operator(); // 操作符:EQ, GT, GE, LT, LE
    @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Documented
    @interface List { CrossFieldMatch[] value(); }
}

2. 實現驗證器 CrossFieldMatchValidator

public class CrossFieldMatchValidator 
    implements ConstraintValidator<CrossFieldMatch, Object> {
    private String firstFieldName, secondFieldName;
    private CrossFieldOperator operator;

    @Override
    public void initialize(CrossFieldMatch constraintAnnotation) {
        firstFieldName = constraintAnnotation.first();
        secondFieldName = constraintAnnotation.second();
        operator = constraintAnnotation.operator();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        try {
            Class<?> clazz = value.getClass();
            Field firstField = clazz.getDeclaredField(firstFieldName);
            Field secondField = clazz.getDeclaredField(secondFieldName);
            firstField.setAccessible(true);
            secondField.setAccessible(true);
            Object firstVal = firstField.get(value);
            Object secondVal = secondField.get(value);
            if (firstVal == null || secondVal == null) return false;
            if (!firstVal.getClass().equals(secondVal.getClass())) return false;

            // 根據操作符比較(支持 Long/Integer/Short/Double)
            switch (operator) {
                case EQ: return firstVal.equals(secondVal);
                case GT: return compare(firstVal, secondVal) > 0;
                case GE: return compare(firstVal, secondVal) >= 0;
                case LT: return compare(firstVal, secondVal) < 0;
                case LE: return compare(firstVal, secondVal) <= 0;
            }
        } catch (Exception e) { /* ignore */ }
        return false;
    }
    // compare 方法省略:按類型轉換後比較
}

3. 使用示例

@CrossFieldMatch.List({
    @CrossFieldMatch(first = "minQty", second = "maxQty", 
                     operator = CrossFieldOperator.LE, 
                     message = "最小數量必須小於等於最大數量")
})
public class ProductPriceQtyRange implements Serializable {
    @Min(value = 0, message = "最小數量不合法")
    private int minQty;
    @Min(value = 0, message = "最大數量不合法")
    private int maxQty;
    // getter/setter
}

Spring MVC 配置啟用校驗

<mvc:annotation-driven validator="validator">
<bean id="validator" 
      class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
  <property name="providerClass" 
            value="org.hibernate.validator.HibernateValidator"/>
  <property name="validationMessageSource" ref="messageSource"/>
</bean>
<bean id="messageSource" 
      class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
  <property name="useCodeAsDefaultMessage" value="false"/>
  <property name="defaultEncoding" value="UTF-8"/>
</bean>

Controller 中使用

@PostMapping("/save")
public Result save(@Valid @RequestBody ProductPriceQtyRange dto, BindingResult result) {
    if (result.hasErrors()) {
        return Result.error(result.getFieldError().getDefaultMessage());
    }
    // 業務邏輯
}

總結

聲明式校驗將數據完整性約束內聚到 DTO 類,業務代碼專注核心邏輯。內置注解覆蓋 80%+ 場景,跨字段校驗通過自定義 @Constraint 實現,配合 @Valid 遞歸校驗嵌套對象,構建完整校驗體系。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved