Data verification: Spring Validation

Spring Validation Overview

During development, we often encounter the need for parameter verification. For example, when a user registers, it is necessary to verify that the user name cannot be empty, the length of the user name does not exceed 20 characters, the mobile phone number is in a legal mobile phone number format, etc. If we use the ordinary method, we will couple the verification code with the real business processing logic, and if we want to add a new verification logic in the future, we will need to modify multiple places. Spring validation allows object verification rules to be defined through annotations, which separates verification from business logic, making code writing more convenient. Spring Validation is actually a further encapsulation of Hibernate Validator to facilitate use in Spring.

There are many verification methods in Spring
The first is to implement the org.springframework.validation.Validator interface and then call this class in the code

The second is to perform verification according to the Bean Validation method, that is, through annotations

The third method is implementation verification based on method

The fourth way to implement custom verification

Implemented by implementing the Validator interface

Create submodule spring6-validator

Introduce dependencies

 <dependencies>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>7.0.5.Final</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>jakarta.el</artifactId>
            <version>4.0.1</version>
        </dependency>
    </dependencies>

Create entity classes, define attributes, and create corresponding set and get methods

public class Person {
    
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Create classes, implement interfaces, implement interface methods, and write verification logic

The supports method is used to indicate which type this check is used on
validate is the place to set the verification logic. ValidatorUtils is a verification tool class encapsulated by Spring to help quickly implement verification.

public class PersonValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        //supports method is used to indicate which type this check is used on
        return Person.class.equals(clazz);
    }

    //Verification rules
    @Override
    public void validate(Object target, Errors errors) {
        //validate is the place to set the verification logic. ValidatorUtils is a verification tool class encapsulated by Spring to help quickly implement verification.

        //name cannot be empty
        ValidationUtils.rejectIfEmpty(errors,"name","name.empty","name is null");

        //age cannot be less than 0 or greater than 100
        Person p = (Person) target;
        if (p.getAge() < 0){
            errors.rejectValue("age","age.value.error","age < 0");
        } else if (p.getAge() > 200) {
            errors.rejectValue("age","age.value.old","age > 200");
        }
    }
}

Complete test

//Verification test
public class TestPerson {

    public static void main(String[] args) {
        //Create person object
        Person person = new Person();

        //Create person corresponding databinder
        DataBinder binder = new DataBinder(person);

        //Set the validator
        binder.setValidator(new PersonValidator());

        //Call method to perform verification
        binder.validate();

        //Output verification results
        BindingResult result = binder.getBindingResult();
        System.out.println(result.getAllErrors());
    }
}

Bean Validation annotation implementation

Using the Bean Validation verification method is how to inject the javax.validation.ValidatorFactory and javax.validation.Validator required for Bean Validation into the container.

Spring has an implementation class by default, LocalValidatorFactoryBean, which implements the interface in Bean Validation above, and also implements the org.springframework.validation.Validator interface.

Create the configuration class ValidationConfig and configure LocalValidatorFactoryBean

@Configuration
@ComponentScan("com.yogurt.spring6.validator.two")
public class ValidationConfig {
    
    @Bean
    public LocalValidatorFactoryBean validator(){
        return new LocalValidatorFactoryBean();
    }
}

Create an entity class, define attributes, create corresponding set and get methods, and use annotations to set verification rules on the attributes

Commonly used annotations

@NotNull The limit must not be null
@NotEmpty only works on string types, the string is not empty, and the length is not 0

@NotBlank only works on string types, the string is not empty, and the string after trim0 is not an empty string @DecimalMax(value) The limit must be a number not greater than the specified value

@DecimalMin(value) The limit must be a number not less than the specified value

@Max(value) The limit must be a number not greater than the specified value

@Min(value) The limit must be a number that is not less than the specified value. The limit must conform to the specified regular expression @Pattern(value)

@size(max,min) limits the character length to between min and max

@Email verifies that the element value of the annotation is Email. You can also specify a custom email format through regular expressions and flags.

public class User {

    @NotNull
    private String name;
    
    @Min(0)
    @Max(150)
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Use annotations to set validation rules on attributes

Create validator

Use native: import jakarta.validation.Validator;

@Service
public class MyValidation1 {

    @Autowired
    private Validator validator;

    private boolean validatorByUserOne(User user){
        Set<ConstraintViolation<User>> validate = validator.validate(user);
        return validate.isEmpty();
    }
}

Use spring framework: import org.springframework.validation.Validator;

@Service
public class MyValidation2 {

    @Autowired
    private Validator validator;

    public boolean validatorByUserTwo(User user){
        BindException bindException = new BindException(user, user.getName());
        validator.validate(user,bindException);
        return bindException.hasErrors();
    }
}

Complete test

Native test:

 @Test
    public void testValidationOne(){
        ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);
        MyValidation1 validation1 = context.getBean(MyValidation1.class);

        User user = new User();
        //user.setName("luck");
        //user.setAge(20);
        boolean message = validation1.validatorByUserOne(user);
        System.out.println(message);
    }

normal result

spring framework test:

 @Test
    public void testValidationTwo(){
        ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);
        MyValidation2 validation2 = context.getBean(MyValidation2.class);

        User user = new User();
// user.setName("luck");
// user.setAge(20);
        boolean message = validation2.validatorByUserTwo(user);
        System.out.println(message);
    }
}

Error reported:
java.lang.IllegalArgumentException: Object name must not be null

Modify–>Set name and age:

 @Test
    public void testValidationTwo(){
        ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);
        MyValidation2 validation2 = context.getBean(MyValidation2.class);

        User user = new User();
        user.setName("luck");
        user.setAge(20);
        boolean message = validation2.validatorByUserTwo(user);
        System.out.println(message);
    }
}

Method-based verification

Create configuration class ValidationConfig

@Configuration
@ComponentScan("com.yogurt.spring6.validator.three")
public class ValidationConfig {
    
    @Bean
    public MethodValidationPostProcessor validationPostProcessor(){
        return new MethodValidationPostProcessor();
    }
}

Create an entity class, define attributes, create corresponding set and get methods, and use annotations to set verification rules on the attributes

public class User {

    @NotNull
    private String name;

    @Min(0)
    @Max(150)
    private int age;

    @Pattern(regexp = "^1(3|4|5|7|8)\d{9}$",message = "Mobile phone number format is wrong")
    @NotBlank(message = "Mobile phone number cannot be blank")
    private String phone;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

Use annotations to set validation rules on attributes

Create validator

@Service
@Validated
public class MyService {

    public String testMethod(@NotNull @Valid User user){
        return user.toString();
    }
}

Complete test

public class TestUser {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);
        MyService service = context.getBean(MyService.class);
        User user = new User();
        //user.setName("lucy");
        //user.setPhone("15545451223");
        service.testMethod(user);
    }
}

Error report: Exception in thread “main” jakarta.validation.ConstraintViolationException: testMethod.arg0.name: cannot be null, testMethod.arg0.phone: mobile phone number cannot be empty

Modify–>Set name and mobile phone number:

public class TestUser {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);
        MyService service = context.getBean(MyService.class);
        User user = new User();
        user.setName("lucy");
        user.setPhone("15545451223");
        service.testMethod(user);
    }
}