Not as well written as I write, not as well written as I write

The introduction

I don’t know how you write the parameter check of controller layer in the usual business development process. Is there a direct judgment like this?

Public String add(UserVO UserVO){if(uservo.getAge () == null){return "Age is not null "; } if(userVo.getAge () > 120){return "Age cannot exceed 120"; } if(userVo.getName ().isEmpty()){return "userName cannot be empty "; } // Omit a bunch of parameter checks... return "OK"; }

The business code has not even started to write, but the parameter validation has written a bunch of judgments. There’s nothing wrong with this, but it just comes across as unpolished and unprofessional.

The Spring framework already encapsulates a set of validation components: Validation. Its characteristics are simple and easy to use, high degree of freedom. Using Spring Boot-2.3.1. Release to build a simple Web project, we will show you how to do parameter validation gracefully, step by step, during development.

1. Environment construction

Starting with Spring Boot-2.3, validation packages have been separated into a starter component (see: Validation-Starter-No-Longer-included -in-Web-Starters), so the following dependencies need to be introduced:

<! Springframework. boot</ grouppid >. Spring Framework boot</ grouppid > <artifactId>spring-boot-starter-validation</artifactId> </dependency> <! --web component --> <dependency> < grouppid >org.springframework.boot</ grouppid > <artifactId>spring-boot-starter web</artifactId> </dependency>

Prior to Spring Boot-2.3, only Web dependencies were introduced.

2. Try your hand

Parameter validation is very simple. First, add validation rule annotation to the field to be validated

Public class UserVO {@notNull (message = "age cannot be empty ") private Integer age; }

You can then add the @ appropriatedly and bindingResult used to receive error messages to the controller method, and you have version 1:

public String add1(@Validated UserVO userVO, BindingResult result) { List<FieldError> fieldErrors = result.getFieldErrors(); if(! fieldErrors.isEmpty()){ return fieldErrors.get(0).getDefaultMessage(); } return "OK"; }

If the parameter does not conform to the rule, the corresponding message message will be returned:

Age cannot be empty

There are many built-in validation annotations, listed below:

annotations Check the function
@AssertFalse Must be false
@AssertTrue It must be true
@DecimalMax Is less than or equal to the given value
@DecimalMin Greater than or equal to the given value
@Digits Maximum number of integers and maximum number of decimals can be set
@Email Verify that it conforms to the Email format
@Future It has to be some time in the future
@FutureOrPresent Current or future time
@Max The maximum
@Min The minimum value
@Negative Negative numbers (excluding 0)
@NegativeOrZero Negative or zero
@NotBlank Is not null and contains at least one non-white space character
@NotEmpty Not null and not null
@NotNull Not null
@Null null
@Past It has to be past time
@PastOrPresent It has to be past time, including the present
@Pattern Regular expressions must be satisfied
@PositiveOrZero Positive or zero
@Size Check the number of elements in the container

3. Canonical return values

When there are many parameters to be validated, we hope to return all validation failure information at one time, so as to facilitate the interface caller to adjust. This requires a unified return format, and the common way is to encapsulate a result class.

public class ResultInfo<T>{ private Integer status; private String message; private T response; // Sort out the code... }

Modify the controller method, version 2:

public ResultInfo add2(@Validated UserVO userVO, BindingResult result) { List<FieldError> fieldErrors = result.getFieldErrors(); List<String> collect = fieldErrors.stream() .map(o -> o.getDefaultMessage()) .collect(Collectors.toList()); Return new resultInfo <>().success(400," request parameter error ", Collect); }

When this method is requested, all error arguments are returned:

{"status": 400, "message": "request parameter error ", "response": [" Age must be between [1,120] ", "BG field integer digits must be at most 3 digits, decimal digits must be at most 1 digit ", "name cannot be empty ", "email format error"]}

4. Global exception handling

It would still be tedious to use if the bindingResult information was written in each Controller method. Validation exceptions can be uniformly handled by global exception handling.

Spring throws an exception when we write the @validated annotation and do not write bindingResult. As a result, you can write a global exception handler class to handle the validation exception uniformly, eliminating the need to reorganize the exception information.

The global exception-handling class simply annotates @RestControllerAdvice on the class and uses the @ExceptionHandler annotation on the method that handles the corresponding exception, indicating which exception to handle.

@RestControllerAdvice public class GlobalControllerAdvice {private static final String BAD_REQUEST_MSG = "Client request parameter error "; @ExceptionHandler(bindException.class) public ResultInfo @ExceptionHandler(bindException. Class) public ResultInfo bindExceptionHandler(BindException e) { List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors(); List<String> collect = fieldErrors.stream() .map(o -> o.getDefaultMessage()) .collect(Collectors.toList()); return new ResultInfo().success(HttpStatus.BAD_REQUEST.value(), BAD_REQUEST_MSG, collect); } / / < 2 > processing json request body call interface check failed the exception thrown @ ExceptionHandler (MethodArgumentNotValidException. Class) public ResultInfo methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) { List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors(); List<String> collect = fieldErrors.stream() .map(o -> o.getDefaultMessage()) .collect(Collectors.toList()); return new ResultInfo().success(HttpStatus.BAD_REQUEST.value(), BAD_REQUEST_MSG, collect); } / / < 3 > deal with a single parameter calibration failure the exception thrown @ ExceptionHandler (ConstraintViolationException. Class) public ResultInfo constraintViolationExceptionHandler(ConstraintViolationException e) { Set<ConstraintViolation<? >> constraintViolations = e.getConstraintViolations(); List<String> collect = constraintViolations.stream() .map(o -> o.getMessage()) .collect(Collectors.toList()); return new ResultInfo().success(HttpStatus.BAD_REQUEST.value(), BAD_REQUEST_MSG, collect); }}

In fact, in the global exception handling class, we can write more than one exception handling method, and the class rep summarizes three possible exceptions that can be thrown when parameters are checked:

  1. The form data method is used to call the interface. The validation exception throws a BindException
  2. Using json request body call interface, check exception thrown MethodArgumentNotValidException
  3. A single parameter validation exception thrown ConstraintViolationException

Note: Single parameter validation needs to add validation annotation on the parameter and annotation on the class
@Validated.

The global Exception handler class can add various exceptions that need to be handled, such as adding an Exception handler to Exception.class that records the Exception and returns a friendly hint when none of the ExceptionHandler can handle it.

5. Group check

If the same parameter requires different validation rules to be applied in different scenarios, group validation is required. For example, if the newly registered user has not yet been named, we allow the name field to be null, but we do not allow the name to be updated with a null character.

Packet check has three steps:

  1. Define a grouping class (or interface)
  2. Add to the validation annotationgroupsProperty specifies the grouping
  3. Controllermethods@ValidatedAnnotations add grouping classes
public interface Update extends Default{
}
Public class UserVo {@NotBlank(message = "name cannot be empty ",groups = update.class) private String name; // Sort out the code... }
@PostMapping("update")
public ResultInfo update(@Validated({Update.class}) UserVO userVO) {
    return new ResultInfo().success(userVO);
}

Careful students may have noticed that the custom Update grouping interface inherits the Default interface. Check annotations (such as: @ NotBlank) and @ validated by Default are Default. The class groups, this point in the javax.mail. Validation. Groups. The Default comments

/**
 * Default Jakarta Bean Validation group.
 * <p>
 * Unless a list of groups is explicitly defined:
 * <ul>
 *     <li>constraints belong to the {@code Default} group</li>
 *     <li>validation applies to the {@code Default} group</li>
 * </ul>
 * Most structural constraints should belong to the default group.
 *
 * @author Emmanuel Bernard
 */
public interface Default {
}

When you write the UPDATE grouping interface, if you inherit Default, the following two expressions are equivalent:

@Validated({Update.class})

@Validated({Update.class,Default.class})

If you request the /update interface, you can see that not only is the Name field validated, but other fields that are in the Default.class group by Default are also validated

{"status": 400, "message": "client request parameter error ", "response": ["name cannot be empty ", "age cannot be empty ", "email cannot be empty"]}

If UPDATE does not inherit DEFAULT, only the @ VALIDATED ({UPDATE.class}) parameter fields that belong to the UPDATE.class group are Validated. Request the interface again after modification and you can see that the other fields are not Validated:

{"status": 400, "message": "client request parameter error ", "response": ["name cannot be empty"]}

6. Recursive check

If you add a property of class OrderVo to the UserVo class that also needs to be validated, recursive validation can be done by adding the @Valid annotation to the corresponding property (the same applies to collections).

OrderVO classes as follows

public class OrderVO { @NotNull private Long id; @NotBlank(message = "ItemName cannot be empty ") private String ItemName; // Sort out the code... }

Add a property of type OrderVo to UserVo class

Public class UserVo {@NotBlank(message = "name cannot be empty ",groups = update.class) private String name; // OrderVo @Valid private OrderVo OrderVo; // Sort out the code... }

The call request verifies as follows:

7. Custom validation

Spring Validation provides us with so many features that it can be used for most parameter validation scenarios in everyday development. However, a good framework must be easy to extend. With the ability to scale, you can handle more complex business scenarios, since the only constant during development is change itself.

Spring Validation allows the user to customize the Validation. The implementation is simple and consists of two steps:

  1. Custom validation annotations
  2. Write the validator class

The code is also very simple, combined with comments you can read it

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, The PARAMETER}) @ Retention (RUNTIME) @ Documented @ the Constraint (validatedBy = {HaveNoBlankValidator. Class}) / / marked by which class to perform validation logic Public @interface HavenoBlank {public @interface HavenoBlank {public @interface HavenoBlank {public @interface HavenoBlank {public @interface HavenoBlank {public @interface HavenoBlank {public @interface HavenoBlank { Class<? >[] groups() default { }; Class<? extends Payload>[] payload() default { }; /** * @target ({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, ANNOTATION_TYPE); TYPE_USE }) @Retention(RUNTIME) @Documented public @interface List { NotBlank[] value(); }}
public class HaveNoBlankValidator implements ConstraintValidator<HaveNoBlank, String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) {/ / null do not test the if (value = = null) {return true; } if (Value.contains (" ")) {return false; } return true; }}

Custom validation annotations work just like built-in annotations. You can add the corresponding annotations to the required fields and students can verify them themselves

review

That’s all you need to do to gracefully validate parameters using Spring Validation. What are the Validation features mentioned in this article

  1. Built-in a variety of commonly used validation annotations
  2. Support for single parameter validation
  3. Automatically assembles validation exceptions in conjunction with global exception handling
  4. Packet check
  5. Support recursive validation
  6. Custom check

The code for this article has been uploaded to GitHub


👇 Follow the Java class representatives to get the latest Java articles