Spring Validated verification framework makes your project more concise and improves development efficiency

Spring Framework provides a set of frameworks that can easily verify the parameters received in the Controller layer, including the @Validated annotation. Using the @Validated annotation in a Spring project allows us to perform parameter validation more conveniently, avoiding the trouble of manual validation, and making the code more elegant and easy to maintain. This article will introduce in detail the method and common application scenarios of using @Validated for parameter validation in Spring projects.

1. Introduction to @Validated annotation

The @Validated annotation is a parameter validation annotation provided in the Spring Framework, which can be used to mark methods, classes, method parameters, and method return values that require parameter validation. By using the @Validated annotation, we can check the input parameters very conveniently, and customize the validation rules and error messages.

Introduce dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2. Use of @Validated annotation

2.1 Use in the Controller layer

Using the @Validated annotation in the Controller layer is the most common usage scenario. By adding the @Validated annotation to the parameter of the Controller method, the parameter can be validated. Here is a simple example:

@RestController
@RequestMapping("/api/user")
public class UserController {<!-- -->
    @PostMapping
    public User createUser(@RequestBody @Validated User user) {<!-- -->
        //...
    }
}

In the above code, the @Validated annotation marks a parameter of the User type, indicating that the parameter needs to be validated. If there are validation annotations in the User class, then these annotations will automatically trigger the validation process. If the validation fails, a MethodArgumentNotValidException will be thrown.

2.2 Custom validation rules

When using the @Validated annotation for parameter validation, we often need to customize validation rules. Spring Framework provides a variety of ways to customize validation rules, including implementing annotations and writing custom Validators.

2.2.1 Implementation using annotations

You can implement custom validation rules by writing annotations. For example, here is an annotation for validating the format of a mobile phone number:

@Target({<!-- -->ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy. RUNTIME)
@Constraint(validatedBy = PhoneValidator. class)
public @interface Phone {<!-- -->
    String message() default "The format of the mobile phone number is incorrect";

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

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

In the above code, the @Phone annotation is used to mark the fields that need to be verified in the mobile phone number format, and the @Constraint annotation specifies the specific verification logic class PhoneValidator. PhoneValidator implements the ConstraintValidator interface to complete the verification of the mobile phone number format.

Next, let’s take a look at the implementation of PhoneValidator:

public class PhoneValidator implements ConstraintValidator<Phone, String> {<!-- -->
    private final static Pattern PHONE_PATTERN = Pattern.compile("^1\d{10}$");

    @Override
    public void initialize(Phone constraintAnnotation) {<!-- -->
    }

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

In the isValid method of PhoneValidator, we use regular expressions to determine whether the format of the mobile phone number is correct. If the format is incorrect, it returns false, and the error message can be set through the context parameter.

The validation rules implemented using the above custom annotations can be easily applied to the Controller layer just like Spring’s own validation annotations.

2.2.2 Writing a custom Validator

In addition to using annotations to implement custom validation rules, you can also write a custom Validator to implement specific validation logic.

Here is a simple example for checking the magnitude relationship of two integers:

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);
        }
    }
}

In the above code, we define a ComparisonValidator class, which implements the ConstraintValidator interface, and defines a validation rule @Comparison.

The verification rule needs to use the two attributes of operator and valueFieldName to determine the specific comparison method and the name of the attribute to be compared. In the isValid method, we first get the compared attribute value otherValue, and then judge whether the value is greater than (less than, equal to) otherValue according to the operator.

Using a custom Validator requires manually creating a validation logic class and associating it with annotations. When used in the Controller layer, we can use custom validation annotations just like Spring’s own validation annotations.

2.3 Combining annotations

Sometimes, we need to perform multiple checks on the same parameter, which can be achieved by using combined annotations. For example, here’s an example of a composite annotation used to validate the password format:

@Target({<!-- -->ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy. RUNTIME)
@Documented
@Constraint(validatedBy = {<!-- -->})
@Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^ & amp; + =])(?=\S + $).{8,}$", message = "The password must contain uppercase and lowercase letters, numbers and special characters, and the length must be at least 8 characters")
@NotNull(message = "Password cannot be empty")
public @interface Password {<!-- -->
    String message() default "Password format is incorrect";

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

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

In the above code, the @Password annotation realizes the format verification of the password by combining the @Pattern and @NotNull annotations. If the password format is incorrect or empty, a validation exception will be thrown.

When using combined annotations, you need to pay attention to whether the combined annotations have been annotated with @Constraint, and don’t forget to set attributes such as message, groups, and payload.

2.4 Unified exception handling

When using @Validated for parameter validation, if the validation fails, a MethodArgumentNotValidException will be thrown. In order to improve the maintainability of the code, we can uniformly handle the verification failure by adding the @ExceptionHandler annotation at the Controller layer and catching the exception.

For example, here is a simple example of exception handling:

@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);
    }
}

In the above code, we uniformly handle MethodArgumentNotValidException exceptions by adding @RestControllerAdvice and @ExceptionHandler annotations to the GlobalExceptionHandler class. In the handleValidationException method, we first get the BindingResult object, and collect error message by traversing all error messages. Finally, a Map object containing error message is returned.

3. Common application scenarios of @Validated annotation

@Validated annotation, as a parameter validation annotation in Spring Framework, is widely used in parameter validation of the Controller layer, parameter validation of DTO classes, and parameter validation of business classes. Here are a few common application scenarios:

  1. Parameter verification during database operations: Database operations generally require parameter verification to avoid security issues such as SQL injection caused by invalid parameters.

  2. Parameter verification of the DTO class: When using the DTO (Data Transfer Object) class for data transfer, it is often necessary to verify the transferred fields to ensure the validity and integrity of the data.

  3. Parameter verification of business classes: methods in business classes usually also need to verify parameters to ensure the correctness and reliability of business logic.

4. Commonly used validation annotations

  1. @NotNull annotation

@NotNull indicates that the annotated parameter cannot be null.

For example:

public void testNotNull(@NotNull String str) {<!-- -->}
  1. @Size annotation

@Size indicates that the size of the annotated parameter must be within the specified range (including the minimum and maximum values).

For example:

public void testSize(@Size(min = 1, max = 10) String str) {<!-- -->}
  1. @Min and @Max annotations

@Min and @Max represent the minimum and maximum values of the annotated parameters, respectively.

For example:

public void testMin(@Min(18) int age) {<!-- -->}

public void testMax(@Max(100) int score) {<!-- -->}
  1. @DecimalMin and @DecimalMax annotations

@DecimalMin and @DecimalMax represent the minimum and maximum values of the annotated parameter, respectively, and are applicable to parameters of type Float, BigDecimal or BigInteger.

For example:

public void testDecimalMin(@DecimalMin("0.00") BigDecimal price) {<!-- -->}

public void testDecimalMax(@DecimalMax("100.00") BigDecimal score) {<!-- -->}
  1. @Digits annotation

@Digits indicates that the annotated parameter must be a number, and the number of integer digits and decimal digits cannot exceed the specified value (the default is 2 integer digits and 0 decimal digits).

For example:

public void testDigits(@Digits(integer = 2, fraction = 1) BigDecimal num) {<!-- -->}
  1. @Email annotation

@Email indicates that the annotated parameter must be a valid email address.

For example:

public void testEmail(@Email String email) {<!-- -->}
  1. @Pattern annotation

@Pattern indicates that the annotated parameter must conform to the specified regular expression pattern.

For example:

public void testPattern(@Pattern(regexp = "^\d{4}-\d{1,2}-\d{1,2}$") String date) {<!-- ->}

In addition to using single annotations, you can also use combined annotations to complete more complex validation logic.

For example:

@NotNull(message = "Username cannot be empty")
@Size(min = 5, max = 20, message = "Username length must be between 5 and 20")
public String getUsername() {<!-- -->
    return this. username;
}

The above code indicates that the username must not be empty and must be within the length range. If the requirements are not met, the corresponding exception will be thrown, such as MethodArgumentNotValidException and so on.