Verification of parameters is essential when writing business code. Hibernate based Validator can be very convenient to implement parameter verification. This article uses SpringBoot as an example to explain how to use the Validator

Basic operation

Maven dependency

The first step is to introduce the Starter dependency of the Validator

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

In this starter, you actually end up relying on Hibernate,

2. Add verification parameters to the entity

Add verification rules for entity objects to be verified

public class Student {

  @NotNull(message = "student id should not be null", groups = UpdateStudentBasicInfo.class)
  private Long id;

  @NotEmpty(message = "student name should not be empty")
  private String name;

  @NotNull(message = "student address should not be null")
  private String address;

  @NotEmpty(message = "student sex should not be empty")
  @Pattern(regexp = "male|female", message = "student sex should match male or female")
  private String sex;

  @NotEmpty(message = "student telephone should not be empty", groups = StudentAdvanceInfo.class)
  private String telephone;
}
Copy the code

Some common rules are used above.

3, request parameters with annotations, enable verification

Implement a Controller that operates on entities and enables validation. Note: This document is for verification only, not for actual service processing.

@PostMapping("/add")
public ApiCommonResponse<String> addStudent(@Valid Student student) {
  return ApiCommonResponse.success("OK");
}
Copy the code
4. Process the verification results

Option 1: Add BindResult directly to the request method for processing

The request method is modified, the verification result is obtained through BindResult, and the response is processed based on the result.

@PostMapping("/add")
public ApiCommonResponse<String> addStudent(@Valid Student student, BindingResult bindResult) {
  if (bindResult.hasErrors()) {
    log.error(bindResult.toString());
    return ApiCommonResponse.fail(HttpStatus.BAD_REQUEST.value(), bindResult.toString());
  }
  return ApiCommonResponse.success("OK");
}
Copy the code

Scheme 2: Use the ExceptionHandler interceptor uniformly to encapsulate the check results (recommended)

This is done primarily with the @RestControllerAdvice and @ExceptionHandler annotations, but there is one caveat.

The Validator throws inconsistent exceptions for json requests and form requests when validation is violated.

@RestControllerAdvice
public class ValidationHandlers {

  /** * The form requests validation result processing *@param bindException
     * @return* /
  @ExceptionHandler(value = BindException.class)
  public ApiCommonResponse<String> errorHandler(BindException bindException) {
    BindingResult bindingResult = bindException.getBindingResult();
    return extractException(bindingResult.getAllErrors());
  }

  /** * JSON request validation result, that is, the request entity is marked@RequestBody
     * @param methodArgumentNotValidException
     * @return* /
  @ExceptionHandler(value = MethodArgumentNotValidException.class)
  public  ApiCommonResponse<String> errorHandler(MethodArgumentNotValidException methodArgumentNotValidException) {
    BindingResult bindingResult = methodArgumentNotValidException.getBindingResult();
    return extractException(bindingResult.getAllErrors());
  }

  private  ApiCommonResponse<String> extractException(List<ObjectError> errorList) {
    StringBuilder errorMsg = new StringBuilder();
    for (ObjectError objectError : errorList) {
      errorMsg.append(objectError.getDefaultMessage()).append(";");
    }
    // Remove the last delimiter
    errorMsg.delete(errorMsg.length() - 1, errorMsg.length());
    returnApiCommonResponse.fail(HttpStatus.BAD_REQUEST.value(), errorMsg.toString()); }}Copy the code
5. Test results
Scenario 1: Add based on form +ExceptionHandler

Scenario 2: In the request parameters, use BindResult directly
@PostMapping("/addWithBindResult")
@ResponseBody
public ApiCommonResponse<String> addStudent(@Valid Student student, BindingResult bindResult) {
  if (bindResult.hasErrors()) {
    log.error(bindResult.toString());
    return ApiCommonResponse.fail(HttpStatus.BAD_REQUEST.value(), bindResult.toString());
  }
  return ApiCommonResponse.success("OK");
}
Copy the code

Advanced usage

1. Verify pathVariable and requestParam in the request

Step 1: Add validation to method parameters

@GetMapping("/getStudentById/{id}")
public ApiCommonResponse<String> getStudentById(@PathVariable("id") @Min(value = 10, message = "input id must great than 10") Long id) {
  return ApiCommonResponse.success("OK");
}

@GetMapping("/getStudentById")
public ApiCommonResponse<String> getStudentByIdRequestParam(@RequestParam @Min(value = 10, message = "input id must great than 10") Long id) {
  return ApiCommonResponse.success("OK");
}
Copy the code

Step 2: Enable validation at the class level

@Controller
@Slf4j
@Validated
public class ValidatorDemoController 
Copy the code

Test Conditions:

Can see at this point, the abnormalities of the capture ConstraintViolationException, so can pass new ExceptionHandler, return the error response of unity.

/** * Check pathVariable and RequestParam *@param constraintViolationException
     * @return* /
@ExceptionHandler(value = ConstraintViolationException.class)
public ApiCommonResponse<String> errorHandler(ConstraintViolationException constraintViolationException) { Set<ConstraintViolation<? >> constraintViolations = constraintViolationException.getConstraintViolations(); String errorMsg = constraintViolations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));
  return ApiCommonResponse.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
}
Copy the code

2, group

Main application scenarios: For the same entity, different verification rules exist in different scenarios. For example, the unique identifier ID can be empty when it is added. However, this value must not be null during modification.

Step 1: Define tag interfaces for different scenarios.

// Add a scene
public interface AddStudentBasicInfo {}Copy the code
// Modify the scene
public interface UpdateStudentBasicInfo {}Copy the code

Step 2: Verify the entity rule by naming the group condition triggered by the entity


@NotNull(message = "student id should not be null", groups = UpdateStudentBasicInfo.class)
private Long id;
Copy the code

Step 3: Specify triggering conditions where validation is required

@PostMapping("/update")
@ResponseBody
public ApiCommonResponse<String> updateStudent(@Validated(UpdateStudentBasicInfo.class) Student student) {
  return ApiCommonResponse.success("OK");
}
Copy the code

It is important to note that if groups is specified, validation will only be performed against the rules in that group. Therefore, if the rule does not specify groups, it belongs to default. class by Default. In this case, you can use the following method to include the rule.

@PostMapping("/update")
@ResponseBody
public ApiCommonResponse<String> updateStudent(@Validated({UpdateStudentBasicInfo.class, Default.class}) Student student) {
  return ApiCommonResponse.success("OK");
}
Copy the code
package javax.validation.groups;

public interface Default {}Copy the code

3, group sequence

When there are multiple groups, the order of verification rules is not fixed. You can specify the verification order in either of the following ways. Here, it’s kind of like combinatorial check.

Here, for example, there will be basic information about the student, and there will be advanced information about the student. During the verification, you need to verify basic information first and then advanced information after passing the verification.

Step 1: Still need to define the advanced and basic information markup interface:

// Advanced information
public interface StudentAdvanceInfo {}// Basic information
public interface StudentBasicInfo {}Copy the code

Step 2: Add to the entity’s group as needed.

@NotEmpty(message = "student name should not be empty", groups = StudentBasicInfo.class)
private String name;

@NotEmpty(message = "student telephone should not be empty", groups = StudentAdvanceInfo.class)
private String telephone;
Copy the code

There are two ways to specify the verification order.

Scheme 1: Specify on the verified entity

@GroupSequence({StudentBasicInfo.class, StudentAdvanceInfo.class, Student.class})
public class Student 
Copy the code

Be sure to include itself in the GroupSequence. Otherwise, an error is reported: XXX must be part of the redefined default group sequence

StudentBasicInfo-> StudentAdvanceInfo->Default StudentBasicInfo-> StudentAdvanceInfo->Default

Option 2: Define a new tag interface and name the sequence. By contrast, this is recommended if you don’t want to affect the Student’s validation behavior globally.

@GroupSequence({StudentBasicInfo.class, StudentAdvanceInfo.class})
public interface ValidateStudentGroupSequence {}Copy the code
@GetMapping("/testGroupSequence")
@ResponseBody
public ApiCommonResponse<String> testGroupSequence(@Validated(ValidateStudentGroupSequence.class) Student student) {
  return ApiCommonResponse.success("OK");
}
Copy the code

If the name attribute is available, the system automatically checks the telephone corresponding to StudentAdvanceInfo.

4. Custom verification

The Validator itself provides many common validations, and you can implement your own custom validations if you need them. In this example, you can actually use the default.

Step 1: Define validation annotations.

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = CustomerValidator.class)
public @interface CustomerValidatorAnnotation {

  /** * Error message of verification violation */
  String message(a) default "{CustomerValidatorAnnotation.message}"; /** * Class
      [] groups() default {}; Class
      [] payload() default {}; }Copy the code

Step 2: Implement the validator, which is the class in @Constraint from step 1


@Slf4j
public class CustomerValidator implements ConstraintValidator<CustomerValidatorAnnotation.String> {

  private static final String CUSTOMER_TEST = "china";

  @Override
  public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
    returns ! =null&& s.startsWith(CUSTOMER_TEST); }}Copy the code

Step 3: Place it on the entity as needed.

@CustomerValidatorAnnotation(message = "student address must start with china")
private String address;
Copy the code

In the following figure, start with China is a user-defined rule.

! [image-20210418234232785](/Users/wuchunjing/Library/Application Support/typora-user-images/image-20210418234232785.png)

5. Some customizations of the Validator
Custom injection Validator
@Bean
public Validator validator(a) {
  ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
    .configure()
    .failFast(false)
    .buildValidatorFactory();
  return factory.getValidator();
}
Copy the code

In this case, the failFast attribute is configured. If failFast is false, all information is verified. If the failFast is set to true, the system immediately terminates the failFast when any condition is not met and returns the current violation error.

Note: It is important to note that if the entity class is inherited, the parent class will trigger validation even if failFast is set to true. That is, failFast is true. It can be understood that each class is verified, that is, each class has at most one check result that violates the constraint.

Use validators in non-controllers

In addition to using validator validation in controllers, you can actually use it in services as well

@Service
@Validated
public class ValidatorService {

  public void testValidateService(@Valid Student student) {}}Copy the code

Note at this point, if violates the constraint will be thrown ConstraintViolationException.

The alternative is an inject-based Validator implementation that handles the validation results itself and does not actively throw exceptions.

@Service
public class ValidatorBeanService {

  @Resource
  private Validator validator;

  public ApiCommonResponse<String> validate(Student student) {
    Set<ConstraintViolation<Student>> constraintViolations = validator.validate(student);
    if (CollectionUtils.isEmpty(constraintViolations)) {
      return ApiCommonResponse.success("OK");
    }
    String errorMsg = constraintViolations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));
    returnApiCommonResponse.fail(HttpStatus.BAD_REQUEST.value(), errorMsg); }}Copy the code
@GetMapping("/testValidatorBean")
@ResponseBody
public ApiCommonResponse<String> testValidatorBean(Student student) {
  validatorBeanService.validate(student);
  return ApiCommonResponse.success("OK");
}
Copy the code

Refer to article reflecmotor. IO /bean-valida…