Use Spring's Validator for validation

Use Spring's Validator for validation

Single object verification

Let us consider a small data object:

import lombok.Data;

@Data
public class Person {

    private String name;
    private int age;
}

We will implement the following two methods to provide the validation behavior of the Person class
org.springframework.validation.Validator interface method:

  • support (Class)-Can this verification procedure verify the instance of the provided Class?
  • validate (Object, org.springframework.validation.Errors)-validate the given object and register with the given Errors object in case of validation error

Implementing Validator is very simple, especially if you know the ValidationUtils helper class that Spring Framework also provides.

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

/**
 * @author Created by niugang on 2020/3/30/16:45
 */
public class PersonValidator implements Validator {

    /**
     * This Validator validates *just* Person instances
     */
    @Override
    public boolean supports(Class<?> clazz) {
        return Person.class.equals(clazz);
    }

    @Override
    public void validate(Object obj, Errors e) {
        ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
        Person p = (Person) obj;
        if (p.getAge() < 0) {
            e.rejectValue("age", "negative value","年龄不能为负值");
        } else if (p.getAge() > 110) {
            e.rejectValue("age", "too old","年龄不能超过110");
        }
    }

}

As you can see, the static rejectIfEmpty (..) method on the ValidationUtils class is used to reject the "name" attribute (if the attribute is null or an empty string). Take a look at the ValidationUtils javadocs to see what functions it provides in addition to the example shown earlier.

controller verification

    @PostMapping(value = "persons")
    public void savePerson(@RequestBody Person person) {
        log.info("请求参数:{}", person);


//        PersonValidator validator = new PersonValidator();
//        if(validator.supports(Person.class)){
//            BindException errors = new BindException(person, "person");
//            validator.validate(person,errors);
//            List<ObjectError> allErrors = errors.getAllErrors();
//            log.info("size="+allErrors.size());
//            for (int i=0;i<allErrors.size();i++) {
//               log.info(allErrors.get(i).getCode());
//            }
//
//        }

        DataBinder binder = new DataBinder(person);
        binder.setValidator(new PersonValidator());
       // validate the target object
        binder.validate();
      // get BindingResult that includes any validation errors
        BindingResult results = binder.getBindingResult();
        log.info("results:{}", results);
    }
Nested object verification

Although a single Validator class can be implemented to validate each nested object in the rich object, it is best to encapsulate the validation logic
object of each nested class in its own Validator implementation. A simple example of a "rich" object is a Customer consisting of two String properties (the first and second names) and a complex Address object. Address objects can be used independently of client objects, so a unique AddressValidator has been implemented. If you want CustomerValidator to reuse the logic contained in the AddressValidator class without copying and pasting, you can dependency inject or instantiate an AddressValidator in your CustomerValidator and use it as follows:

import lombok.Data;

/**
 * @author Created by niugang on 2020/3/30/18:42
 */
@Data
public class Address {

    private String  location;
}

import lombok.Data;

/**
 * @author Created by niugang on 2020/3/30/18:40
 */
@Data
public class Customer {

    private String firstName;
    private String surname;

    private Address address;
}

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

/**
 * @author Created by niugang on 2020/3/30/18:44
 */
public class AddressValidator implements Validator {

    /**
     * This Validator validates *just* Address instances
     */
    @Override
    public boolean supports(Class<?> clazz) {
        return Address.class.equals(clazz);
    }

    @Override
    public void validate(Object obj, Errors e) {
        ValidationUtils.rejectIfEmpty(e, "location", "location.empty");
        Address p = (Address) obj;
        if (p != null && p.getLocation() != null && p.getLocation().length() > 5) {
            e.rejectValue("location", "value too length", "长度不能超过5");
        }
    }
}
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

/**
 * @author Created by niugang on 2020/3/30/18:47
 */
public class CustomerValidator implements Validator {
    private final Validator addressValidator;

    public CustomerValidator(Validator addressValidator) {

        if (addressValidator == null) {
            throw new IllegalArgumentException("The supplied [Validator] is " +
                    "required and must not be null.");
        }
        if (!addressValidator.supports(Address.class)) {
            throw new IllegalArgumentException("The supplied [Validator] must " +
                    "support the validation of [Address] instances.");
        }
        this.addressValidator = addressValidator;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return Customer.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
        Customer customer = (Customer) target;
        try {
            errors.pushNestedPath("address");
            ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
        } finally {
            errors.popNestedPath();
        }
    }
}

Parse the code into an error message

We have already discussed data binding and validation. Outputting the message corresponding to the verification error is the last thing we need to discuss. In the example shown above, we rejected the name and age fields. If we want to use MessageSource to output an error message, we will use the error code ("name" and "age" in this case) given when rejecting the field. When you call (directly or indirectly, such as the ValidationUtils class) rejectValue or one of the other rejection methods from the Errors interface, the underlying implementation will register not only the code you pass in, but also many other error codes. The error codes it registers are determined by the MessageCodesResolver used. By default, the DefaultMessageCodesResolver is used, for example, it not only registers the message with the code you provide, but also includes the message that contains the name of the field you pass to the reject method. Therefore, if you use rejectValue ("age", "too.darn.old") to reject the field, in addition to the too.darn.old code, Spring will also register too.darn.old.age and too.darn.old.age .int (so the first will contain the field name and the second will contain the field type); this is done to facilitate developers to help them locate error messages, etc.

Spring Validator

Spring 3 has made some enhancements to its verification support. First, the JSR-303 Bean authentication API is now fully supported. Second, when used programmatically, Spring's DataBinder can now validate objects and bind to them. Third, Spring MVC now supports declarative verification @Controller input.

JSR-303 Bean Authentication API Overview

JSR-303 standardizes the verification constraint declaration and metadata of the Java platform. Using this API, you can enforce declarative validation constraints and runtime annotation domain model attributes. You can take advantage of many built-in constraints. You can also define your own custom constraints.

Single object

To illustrate this, consider a simple PersonForm model with two properties:

public class PersonForm {
private String name;
private int age;
}

JSR-303 allows you to define declarative verification constraints for the following attributes:

public class PersonForm {
@NotNull
@Size(max=64)
private String name;
@Min(0)
private int age;
}
 @PostMapping(value = "personforms.do")
 public void saveCustomer(@RequestBody @Validated PersonForm personForm) {
     log.info("请求参数:{}", personForm);
 }

Cascade check

The Bean Validation API not only allows to validate a single class instance, but can also complete an object graph (cascading validation). To do this, simply comment to indicate one,
as shown in cascading validation, use @Valid to refer to another object.

import lombok.Data;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

/**
 * @author Created by niugang on 2020/3/30/19:42
 */
@Data
public class Car {

    @NotNull
    private String type;

    @NotNull
    @Valid
    private PersonInfo driver;
}

import lombok.Data;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

/**
 * @author Created by niugang on 2020/3/30/19:42
 */
@Data
public class PersonInfo {

    @NotNull
    @Size(min = 5)
    private String name;
}


Cascade check set

import lombok.Data;
import org.hibernate.validator.constraints.NotBlank;

import javax.validation.constraints.Min;

/**
 * @author Created by niugang on 2020/4/2/11:32
 */
@Data
public class Dog {


    @NotBlank
    private  String  name;

    @NotBlank
    private  String type;

    @Min(0)
    private int age;
}

import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Created by niugang on 2020/4/2/11:31
 */
@Data
public class Animal {


    @Min(1)
    private int count;


    @Valid
    @NotNull
    @Size(min = 1)
    private List<Dog> dogs= new ArrayList<>();

}

Group check

The class Driver in Driver extends Person and adds attributes age and hasDrivingLicense. The driver must be at least 18 years old (@Min (18)) and have a driving license (@AssertTrue). The two constraints defined on these attributes belong to the group DriverChecks is just a simple marking interface.

import lombok.Data;

import javax.validation.constraints.NotNull;

/**
 * @author Created by niugang on 2020/4/2/14:44
 */
@Data
public class Person {
    @NotNull
    private String name;
}

import lombok.Data;

import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Min;

/**
 * @author Created by niugang on 2020/4/2/14:45
 */
@Data
public class Driver extends Person {
    @Min(
            value = 18,
            message = "You have to be 18 to drive a car",
            groups = DriverChecks.class
    )
    public int age;
    @AssertTrue(
            message = "You first have to pass the driving test",
            groups = DriverChecks.class
    )
    public boolean hasDrivingLicense;
}

/**
 * @author Created by niugang on 2020/4/2/14:45
 */
public interface DriverChecks {
}

controller

The following request will only verify the attributes marked as DriverChecks

  @PostMapping(value = "drivers.do")
    public void cars(@RequestBody @Validated(DriverChecks.class) Driver driver, BindingResult result) {
        log.info("请求参数:{}", driver);
        log.info("请求参数 result:{}", result);
    }

Configure Bean Authentication Provider

Spring provides full support for the Bean Validation API. This includes convenient support for guiding JSR-303 / JSR-349 Bean validation providers as Spring Beans. This allows javax.validation.ValidatorFactory or javax.validation.Validator to be inserted anywhere in the application where validation is required.

Use LocalValidatorFactoryBean to configure the default validator as a Spring Bean:

<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

The basic configuration above will trigger Bean verification to initialize using its default bootstrap mechanism. It is expected that JSR-303 / JSR-349 providers such as Hibernate Validator will appear in the classpath and will automatically detect the provider.

Injection validator

LocalValidatorFactoryBean implements both javax.validation.ValidatorFactory and javax.validation.Validator as well as Spring's
org.springframework.validation.Validator. You can inject a reference to one of these two interfaces into a bean that needs to call validation logic.

If you want to use Bean Validation, please inject the reference API to javax.validation.Validator directly:

import javax.validation.Validator;
@Service
public class MyService {
@Autowired
private Validator validator;

If your bean requires the Spring Validation API, please inject a reference to org.springframework.validation.Validator:

@RestController
@RequestMapping("/validator/")
@Slf4j
public class TestValidatorController{
    
    private final Validator validator;

    @Autowired
    public TestValidatorController(Validator validator) {
        this.validator = validator;
    }
    
      @PostMapping(value = "v2/drivers.do")
      public void carss(@RequestBody Driver driver) {
        BindException bindException = new BindException(driver, "driver");
        validator.validate(driver,bindException);
        log.info("请求参数:{}", driver);
        log.info("请求参数 result:{}", bindException.getBindingResult());
    }

}
@Data
public class Driver {

    @NotNull
    private String name;
    @Min(
            value = 18,
            message = "You have to be 18 to drive a car"
    )

    public int age;

    @AssertTrue(
            message = "You first have to pass the driving test"
    )

    public boolean hasDrivingLicense;

}

Configure custom constraints

Each bean validation constraint consists of two parts. First, the @Constraint annotation declares constraints and their configurable properties. Second, implement the javax.validation.ConstraintValidator interface that implements constrained behavior. To associate the declaration with the implementation, each @Constraint annotation references a corresponding ValidationConstraint implementation class. At runtime, when constraint annotations are encountered in the domain model, ConstraintValidatorFactory instantiates the referenced implementation.

By default, LocalValidatorFactoryBean configures a SpringConstraintValidatorFactory that uses Spring to create a ConstraintValidator instance. This allows your custom ConstraintValidators to benefit from dependency injection like any other Spring Bean

Shown below is an example of a custom @Constraint declaration, followed by an associated ConstraintValidator implementation that uses Spring for dependency injection:

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MyConstraintValidator.class)
public @interface MyConstraint {
}
import javax.validation.ConstraintValidator;
public class MyConstraintValidator implements ConstraintValidator {
@Autowired;
private Foo aDependency;
...
}

As you can see, the ConstraintValidator implementation may have a dependency of @Autowired like any other Spring bean.

Spring-based method verification

That is to write a comment on the method to verify the parameters

The method validation function supported by Bean Validation 1.1 and the custom extension of Hibernate Validator 4.3 can also be integrated into the Spring context in the following way: MethodValidationPostProcessor bean definition:

<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

In order to be eligible for Spring-driven method validation, all target classes need to be annotated with Spring's @Validated annotation, and can optionally declare the validation group to use. Take a look at the setting details of the Hibernate Validator and Bean Validation 1.1 providers using MethodValidationPostProcessor javadocs

Configure a DataBinder

Starting with Spring 3, you can use Validator to configure DataBinder instances. After configuration, you can call Validator by calling binder.validate (). Any verification errors are automatically added to the BindingResult.
When using DataBinder programmatically, it can be used to call verification logic after binding to the target object:

Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());
// bind to the target object
binder.bind(propertyValues);
// validate the target object
binder.validate();
// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();

Insert picture description here

Guess you like

Origin www.cnblogs.com/niugang0920/p/12689224.html