Spring(九):数据校验 Validation

在开发中,我们经常遇到参数校验的需求,比如用户注册的时候,要校验用户名不能为空、用户名长度不超过 20 个字符、手机号是合法的手机号格式等等。如果使用普通方式,我们会把校验的代码和真正的业务处理逻辑耦合在一起,而且如果未来要新增一种校验逻辑也需要在修改多个地方。而 Spring validation 允许通过注解的方式来定义对象校验规则,把校验和业务逻辑分离开,让代码编写更加方便。 Spring Validation 其实就是对 Hibernate Validator 进一步的封装,方便在 Spring 中使用。

在Spring中有多种校验的方式

  • 第一种是通过实现 org.springframework.validation.Validator 接口,然后在代码中调用这个类
  • 第二种是按照 Bean Validation 方式来进行校验,即通过注解的方式。
  • 第三种是基于方法实现校验
  • 除此之外,还可以实现自定义校验

通过实现 Validator 接口实现校验

  1. 创建模块、引入依赖

    <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>
    
  2. 创建实体类,定义属性和 getter、setter 方法

    package site.penghao.spring6.bean;
    
    /**
     * @author hope
     * @date 2023/4/4 - 11:33
     */
    public class Person {
        private String name;
        private Integer age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    }
    
  3. 创建类,实现 Validator 接口,编写校验逻辑

    package site.penghao.spring6.validator;
    
    import org.springframework.validation.Errors;
    import org.springframework.validation.ValidationUtils;
    import org.springframework.validation.Validator;
    import site.penghao.spring6.bean.Person;
    
    /**
     * @author hope
     * @date 2023/4/4 - 11:36
     */
    public class PersonValidator implements Validator {
        @Override
        public boolean supports(Class<?> clazz) {
            // 判断能否使用此校验规则
            return Person.class.equals(clazz);
        }
    
        @Override
        public void validate(Object target, Errors errors) {
            // 校验规则:
            // name 和 age 不能为空,如果为空,将添加错误编码和错误信息
            ValidationUtils.rejectIfEmpty(errors, "name", "name.empty", "name is null");
    
            // age 不能为空,不能小于 0,不能大于 200
            Person p = (Person) target;
            if (p.getAge() == null) {
                errors.rejectValue("age", "age.empty", "age is null");
            } else if (p.getAge() < 0) {
                errors.rejectValue("age", "age.value.error", "age < 0");
            } else if (p.getAge() > 200) {
                errors.rejectValue("age", "age.value.error", "age > 200");
            }
        }
    }
    
  4. 测试校验

    @Test
    public void test() {
        // 创建 Person
        Person person = new Person();
        person.setName("爱尔奎特·布伦史塔德");
        person.setAge(900);
    
        // 创建 person 对应的 data binder
        DataBinder binder = new DataBinder(person);
    
        // 设置校验器
        binder.setValidator(new PersonValidator());
    
        // 调用方法执行校验
        binder.validate();
    
        // 输出校验结果
        BindingResult result = binder.getBindingResult();
        System.out.println(result.getAllErrors());// [Field error in object 'target' on field 'age': rejected value [800]; codes [age.value.error.target.age,age.value.error.age,age.value.error.java.lang.Integer,age.value.error]; arguments []; default message [age > 200]]
    }
    

基于 ValidatorFactoryBean 注解实现

  1. 创建模块、引入依赖

  2. 创建配置类,配置 LocalValidatorFactoryBean

    package site.penghao.spring6.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
    
    /**
     * @author hope
     * @date 2023/4/4 - 12:05
     */
    @Configuration
    @ComponentScan("site.penghao.spring6")
    public class ValidationConfig {
        @Bean
        public LocalValidatorFactoryBean getValidator() {
            return new LocalValidatorFactoryBean();
        }
    }
    

    由于 LocalValidatorFactoryBean 实现了 ValidatorFactoryBean 接口,当需要一个 Validator 类型的 Bean 时,调用 getValidator 方法。

  3. 创建实体类,定义属性,生成 getter 和 setter 方法,在属性上面使用注解设置校验规则

    public class Person {
        @NotNull
        private String name;
        @NotNull
        @Min(0)
        @Max(200)
        private Integer age;
        
        // getter and setter
    }
    
  4. 创建校验器

    方式一:使用 jakarta.validation.Validator

    @Component
    public class PersonValidator1 {
        @Autowired
        private Validator validator;
    
        public boolean validate(Person person) {
            Set<ConstraintViolation<Person>> validate = validator.validate(person);
            return validate.isEmpty();
        }
    }
    

    方式二:使用 Spring 进一步封装的 org.springframework.validation.Validator

    @Component
    public class PersonValidator2 {
        @Autowired
        private Validator validator;
    
        public boolean validate(Person person) {
            BindException bindException = new BindException(person, person.getName());
            validator.validate(person, bindException);
            return !bindException.hasErrors(); // 有错返回 false,没错返回 true
        }
    }
    
  5. 测试

    @Test
    public void test1() {
        ApplicationContext context =
                new AnnotationConfigApplicationContext(ValidationConfig.class);
        PersonValidator1 validator1 = context.getBean(PersonValidator1.class);
        Person person = new Person();
        person.setName("爱尔奎特·布伦史塔德");
        person.setAge(900);
        boolean msg = validator1.validate(person);
        System.out.println(msg); // false
    }
    
    @Test
    public void test2() {
        ApplicationContext context =
                new AnnotationConfigApplicationContext(ValidationConfig.class);
        PersonValidator2 validator2 = context.getBean(PersonValidator2.class);
        Person person = new Person();
        person.setName("爱尔奎特·布伦史塔德");
        person.setAge(17);
        boolean msg = validator2.validate(person);
        System.out.println(msg); // true
    }
    

基于方法实现校验

  1. 创建模块、引入依赖

  2. 创建配置类,配置 MethodValidationPostProcessor

    @Configuration
    @ComponentScan("site.penghao.spring6")
    public class MethodValidationConfig {
        @Bean
        public MethodValidationPostProcessor getValidationPostProcessor() {
            return new MethodValidationPostProcessor();
        }
    }
    

    顾名思义,它的含义是方法执行前启动执行这个后置处理器,进行方法的参数的校验。

  3. 创建实体类,使用注解设置校验规则

    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 = "手机号码格式错误")
        @NotBlank(message = "手机号码不能为空")
        private String phone;
    	
    	// getter and setter
    	// toString()
    }
    
  4. 定义 Service 类,通过注解操作对象

    @Service
    @Validated  // 表示开启基于方法进行校验
    public class MyService {
        // @NotNull 表示非空,@Valid 表示需要符合在 User 中定义的校验规则
        public String testMethod(@NotNull @Valid User user) {
            return user.toString();
        }
    }
    
  5. 测试

    @Test
    void testMethod() {
        ApplicationContext context =
                new AnnotationConfigApplicationContext(ValidationConfig.class);
        MyService service = context.getBean(MyService.class);
        User user = new User();
        String s = service.testMethod(user);
        System.out.println(s);
    }
    

自定义校验

  1. 自定义校验注解

    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Constraint(validatedBy = {CannotBlankValidator.class})
    public @interface CannotBlank {
        //默认错误消息
        String message() default "不能包含空格";
    
        //分组
        Class<?>[] groups() default {};
    
        //负载
        Class<? extends Payload>[] payload() default {};
    
        //指定多个时使用
        @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        @interface List {
            CannotBlank[] value();
        }
    }
    
    
  2. 实现前面指定的 CannotBlankValidator 校验类

    public class CannotBlankValidator implements ConstraintValidator<CannotBlank, String> {
        @Override
        public void initialize(CannotBlank constraintAnnotation) {
        }
    
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            //null时不进行校验
            if (value != null && value.contains(" ")) {
                //获取默认提示信息
                String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate();
                System.out.println("default message :" + defaultConstraintMessageTemplate);
                //禁用默认提示信息
                context.disableDefaultConstraintViolation();
                //设置提示语
                context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation();
                return false;
            }
            return true;
        }
    }
    
  3. 测试(可以使用基于注解或者基于方法的实现来校验)

小结

  • 数据校验用于校验某个属性的值是否符合要求,使用 Spring Validation 可以将校验逻辑和业务代码解耦,Spring Validation 是基于 Hibernate Validation 的,在它上面进行了进一步的封装。
  • 通过直接实现 org.springframework.validation.Validator 接口进行校验。
  • 通过在需要校验的属性添加各种校验属性,并在配置文件或配置类中设置 LocalValidatorFactoryBean。在校验类中注入前面设置的 Validator,使用这个 Validator 进行校验。
  • 通过在需要校验的属性添加各种校验属性,并在配置文件或配置类中设置 MethodValidationPostProcessor,然后在需要校验的方法参数前面设置 @Valid 进行校验。
  • 通过自定义校验注解进行校验。