天天看點

Spring Validated 校驗架構,讓你的項目更簡潔,提升開發效率

作者:老誠不bug
Spring Validated 校驗架構,讓你的項目更簡潔,提升開發效率

Spring Framework 提供了一套可以友善地對 Controller 層中接收的參數進行校驗的架構,其中就包括了 @Validated 注解。在 Spring 項目中使用 @Validated 注解可以讓我們更加友善地進行參數校驗,避免了手動校驗的麻煩,并且使得代碼更加優雅和易于維護。本文将詳細介紹在 Spring 項目中使用 @Validated 進行參數校驗的方法和常見應用場景。

一、@Validated 注解簡介

@Validated 注解是 Spring Framework 中提供的一個參數校驗注解,它可以用來标記需要進行參數校驗的方法、類、方法參數和方法傳回值等地方。通過使用 @Validated 注解,我們可以非常友善地對入參進行檢查,并且可以自定義校驗規則和錯誤提示資訊。

引入依賴

maven複制代碼<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
           

二、@Validated 注解的使用

2.1 在 Controller 層中使用

在 Controller 層中使用 @Validated 注解是最為常見的使用場景。通過在 Controller 方法的參數上添加 @Validated 注解,可以對該參數進行校驗。下面是一個簡單的例子:

java複制代碼@RestController
@RequestMapping("/api/user")
public class UserController {
    @PostMapping
    public User createUser(@RequestBody @Validated User user) {
        // ...
    }
}
           

在上述代碼中,@Validated 注解标記了 User 類型的參數,表示需要對該參數進行校驗。如果 User 類中存在校驗注解,那麼這些注解會自動觸發校驗過程。如果校驗不通過,則會抛出 MethodArgumentNotValidException 異常。

2.2 自定義校驗規則

在使用 @Validated 注解進行參數校驗時,我們常常需要自定義校驗規則。Spring Framework 提供了多種自定義校驗規則的方式,包括使用注解實作、編寫自定義 Validator 等方式。

2.2.1 使用注解實作

可以通過編寫注解來進行自定義校驗規則的實作。例如,下面是一個用于校驗手機号碼格式的注解:

java複制代碼@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
    String message() default "手機号碼格式不正确";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}
           

在上述代碼中,@Phone 注解用于标記需要進行手機号碼格式校驗的字段,而 @Constraint 注解指定了具體的校驗邏輯類 PhoneValidator。PhoneValidator 實作了 ConstraintValidator 接口,完成對手機号碼格式的校驗。

接下來,我們來看一下 PhoneValidator 的實作:

java複制代碼public class PhoneValidator implements ConstraintValidator<Phone, String> {
    private final static Pattern PHONE_PATTERN = Pattern.compile("^1\\d{10}#34;);

    @Override
    public void initialize(Phone constraintAnnotation) {
    }

    @Override
    public boolean isValid(String phone, ConstraintValidatorContext context) {
        return StringUtils.isEmpty(phone) || PHONE_PATTERN.matcher(phone).matches();
    }
}
           

在 PhoneValidator 的 isValid 方法中,我們使用了正規表達式來判斷手機号碼的格式是否正确。如果格式不正确,則傳回 false,并且可以通過 context 參數來設定錯誤提示資訊。

使用上述自定義注解實作的校驗規則,可以和 Spring 自帶的校驗注解一樣,友善地被應用到 Controller 層中。

2.2.2 編寫自定義 Validator

除了使用注解來實作自定義校驗規則以外,還可以編寫自定義 Validator 來實作具體的校驗邏輯。

下面是一個簡單的示例,用于校驗兩個整數的大小關系:

java複制代碼public class ComparisonValidator implements ConstraintValidator<Comparison, Object> {
    private Comparison.Operator operator;
    private String valueFieldName;

    @Override
    public void initialize(Comparison constraintAnnotation) {
        operator = constraintAnnotation.operator();
        valueFieldName = constraintAnnotation.valueFieldName();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }
        try {
            Field valueField = value.getClass().getDeclaredField(valueFieldName);
            valueField.setAccessible(true);
            Object otherValue = valueField.get(value);
            if (otherValue == null) {
                return true;
            }
            int result = ((Comparable) value).compareTo(otherValue);
            switch (operator) {
                case GREATER_THAN:
                    return result > 0;
                case LESS_THAN:
                    return result < 0;
                case GREATER_THAN_OR_EQUAL_TO:
                    return result >= 0;
                case LESS_THAN_OR_EQUAL_TO:
                    return result <= 0;
                default:
                    throw new IllegalArgumentException("Unsupported Comparison Operator: " + operator);
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}
           

在上述代碼中,我們定義了一個 ComparisonValidator 類,它實作了 ConstraintValidator 接口,并且自定義了一個校驗規則 @Comparison。

該校驗規則需要通過 operator 和 valueFieldName 兩個屬性來确定具體的比較方式和被比較的屬性名稱。在 isValid 方法中,我們首先擷取到被比較的屬性值 otherValue,然後根據 operator 來判斷 value 是否大于(小于、等于)otherValue。

使用自定義 Validator 需要手動建立校驗邏輯類,并将其與注解進行關聯。在 Controller 層使用時,我們可以像使用 Spring 自帶的校驗注解一樣來使用自定義的校驗注解。

2.3 組合注解

有時候,我們需要對同一個參數進行多種校驗,這時候可以使用組合注解的方式來實作。例如,下面是一個用于校驗密碼格式的組合注解示例:

java複制代碼@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {})
@Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=])(?=\\S+$).{8,}#34;, message = "密碼必須包含大小寫字母、數字和特殊字元,長度至少為 8 位")
@NotNull(message = "密碼不能為空")
public @interface Password {
    String message() default "密碼格式不正确";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}
           

在上述代碼中,@Password 注解通過組合 @Pattern 和 @NotNull 注解來實作了密碼的格式校驗。如果密碼格式不正确或者為空,則會抛出校驗異常。

在使用組合注解時,需要注意被組合的注解是否已經使用了 @Constraint 注解,并且不要忘記設定 message、groups 和 payload 等屬性。

2.4 統一異常處理

在使用 @Validated 進行參數校驗時,如果校驗失敗,會抛出 MethodArgumentNotValidException 異常。為了提高代碼的可維護性,我們可以通過在 Controller 層添加 @ExceptionHandler 注解并捕獲該異常,來統一處理校驗失敗的情況。

例如,下面是一個簡單的異常處理示例:

java複制代碼@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Map<String, Object> handleValidationException(MethodArgumentNotValidException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        List<String> errorList = bindingResult.getAllErrors().stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.toList());
        return Collections.singletonMap("message", errorList);
    }
}
           

在上述代碼中,我們通過在 GlobalExceptionHandler 類中添加 @RestControllerAdvice 和 @ExceptionHandler 注解來統一處理 MethodArgumentNotValidException 異常。在 handleValidationException 方法中,我們首先擷取到 BindingResult 對象,并通過周遊所有錯誤資訊來收集錯誤提示資訊。最終傳回一個包含錯誤提示資訊的 Map 對象。

三、@Validated 注解的常見應用場景

@Validated 注解作為 Spring Framework 中的一個參數校驗注解,廣泛應用于 Controller 層的參數校驗、DTO 類的參數校驗和業務類的參數校驗等方面。下面列舉了幾個常見的應用場景:

  1. 資料庫操作時的參數校驗:資料庫操作一般需要對參數進行校驗,避免因為無效參數導緻的 SQL 注入等安全問題。
  2. DTO 類的參數校驗:在使用 DTO(Data Transfer Object)類進行資料傳輸時,往往需要對傳輸的字段進行校驗,保證資料的有效性和完整性。
  3. 業務類的參數校驗:業務類中的方法通常也需要對參數進行校驗,以確定業務邏輯的正确性和可靠性。

四、常用的驗證注解

  1. @NotNull 注解

@NotNull 表示被注解的參數不能為 null。

例如:

java複制代碼public void testNotNull(@NotNull String str) {}
           
  1. @Size 注解

@Size 表示被注解的參數的大小必須在指定的範圍内(包括最小值和最大值)。

例如:

java複制代碼public void testSize(@Size(min = 1, max = 10) String str) {}
           
  1. @Min 和 @Max 注解

@Min 和 @Max 分别表示被注解的參數的最小值和最大值。

例如:

java複制代碼public void testMin(@Min(18) int age) {}

public void testMax(@Max(100) int score) {}
           
  1. @DecimalMin 和 @DecimalMax 注解

@DecimalMin 和 @DecimalMax 分别表示被注解的參數的最小值和最大值,适用于浮點數、BigDecimal 或 BigInteger 類型的參數。

例如:

java複制代碼public void testDecimalMin(@DecimalMin("0.00") BigDecimal price) {}

public void testDecimalMax(@DecimalMax("100.00") BigDecimal score) {}
           
  1. @Digits 注解

@Digits 表示被注解的參數必須是一個數字,并且整數位和小數位的位數不能超過指定的值(預設整數位 2 位,小數位 0 位)。

例如:

java複制代碼public void testDigits(@Digits(integer = 2, fraction = 1) BigDecimal num) {}
           
  1. @Email 注解

@Email 表示被注解的參數必須是一個合法的電子郵件位址。

例如:

java複制代碼public void testEmail(@Email String email) {}
           
  1. @Pattern 注解

@Pattern 表示被注解的參數必須符合指定的正規表達式模式。

例如:

java複制代碼public void testPattern(@Pattern(regexp = "^\\d{4}-\\d{1,2}-\\d{1,2}#34;) String date) {}
           

除了使用單個注解外,也可以使用組合注解來完成更為複雜的校驗邏輯。

例如:

java複制代碼@NotNull(message = "使用者名不能為空")
@Size(min = 5, max = 20, message = "使用者名長度必須在 5 到 20 之間")
public String getUsername() {
    return this.username;
}
           

上述代碼表示要求使用者名不能為空,并且在長度範圍内。如果不符合要求,則會抛出相應的異常,如 MethodArgumentNotValidException 等。

連結:https://juejin.cn/post/7236654133020983357

繼續閱讀