夯实Spring系列|第十八章:Spring Validation 效验

夯实Spring系列|第十八章:Spring Validation 效验

本章说明

本章内容有和 第十七章:Spring 国际化(i18n) 以及下一章 [Spring 数据绑定] 都有紧密关联

1.项目环境

2.Spring 效验使用场景

Spring 常规效验(Validator)

Spring 数据绑定(DataBinder)

Spring Web 参数绑定(WebDataBinder)

Spring WebMVC/WebFlux 处理方法参数效验

3.相关接口

3.1 Validator 接口设计

接口职责

  • Spring 内部效验器接口,通过编程的方式效验目标对象

核心方法

  • supports(Class):效验目标类能否效验
  • validate(Object,Errors):效验目标对象,并将效验失败的内容输出至 Errors 对象

配套组件

  • 错误收集器:org.springframework.validation.Errors
  • Validator 工具类:org.springframework.validation.ValidationUtils

3.2 Errors 接口设计

接口职责

  • 数据绑定和效验错误收集接口,与 Java Bean 和其属性有强关联性

核心方法

  • reject 方法(重载):收集错误文案
  • rejectValue 方法(重载):收集对象字段中的错误文案

配套组件

  • Java Bean 错误描述:org.springframework.validation.ObjectError
  • Java Bean 属性错误描述:org.springframework.validation.FieldError

3.3 Errors 文案来源

Errors 文案生产步骤

  • 选择 Errors 实现(如:org.springframework.validation.BeanPropertyBindingResult)
  • 调用 reject 或者 rejectValue 方法
  • 获取 Errors 对象中 ObjectError 或者 FieldError
  • 将 ObjectError 或者 FieldError 中的 code 和 args,关联 MessageSource 实现(如:ResourceBundleMessageSource)

3.4 示例

public class ErrorsMessageDemo {
    public static void main(String[] args) {
        // 0.创建 User 对象
        User user = new User();
        // 1.选择 Errors - BeanPropertyBindingResult
        Errors errors = new BeanPropertyBindingResult(user, "user");
        // 2.调用 reject 或者 rejectValue
        errors.reject("user.properties.not.null");
        errors.rejectValue("name","name.required");
        // reject 生成 ObjectError
        // rejectValue 生成 FieldError
        // 3.获取 Errors 中 ObjectError 和 FieldError
        // FieldError 继承 ObjectError
        List<ObjectError> globalErrors = errors.getGlobalErrors();
        List<FieldError> fieldErrors = errors.getFieldErrors();
        List<ObjectError> allErrors = errors.getAllErrors();
        // 4.通过 ObjectError 和 FieldError 中的 code 和 args 来关联 MessageSource 实现
        MessageSource messageSource = createMessageSource();

        for (ObjectError error : allErrors) {
            String message = messageSource.getMessage(error.getCode(),error.getArguments(),Locale.getDefault());
            System.out.println(message);
        }
    }

    static MessageSource createMessageSource() {
        StaticMessageSource messageSource = new StaticMessageSource();
        messageSource.addMessage("user.properties.not.null", Locale.getDefault(), "User 所有属性不能为空");
        messageSource.addMessage("name.required", Locale.getDefault(), "the name of User must not be null");
        messageSource.addMessage("id.required", Locale.getDefault(), "the id of User must not be null");
        return messageSource;
    }
}

执行结果:

User 所有属性不能为空
the name of User must not be null

3.5 结论

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kRfAHgDe-1593223922262)(G:\workspace\learn-document\spring-framework\csdn\image-20200626211911074.png)]

  • 从调试点可以看出 allErrors 包含了 globalErrors 和 fieldErrors。

  • Errors 对象不能直接输出国际化的文案,但是它可以输出 Code 和 Arguments,我们再通过 MessageSource 进行关联来获得相关的国际化的文案。

4.自定义 Validator

4.1 实现 org.springframework.validation.Validator

  • 实现 supports 方法
  • 实现 validate 方法
    • 通过 Errors 对象收集错误
      • ObjectError:对象(Bean)错误
      • FieldError:对象(Bean)属性(Property)错误
    • 通过 ObjectError 和 FieldError 关联 MessageSource 获得最终的文案

4.2 示例

public class ValidatorDemo {
    public static void main(String[] args) {
        // 1.创建 Validator
        Validator validator = new UserValidator();
        // 2.判断是否支持目标对象的类型
        User user = new User();
        System.out.println("user 对象是否被 UserValidator 支持:"+validator.supports(user.getClass()));
        // 3.创建 Errors 对象
        Errors errors =  new BeanPropertyBindingResult(user,"user");
        // 4.创建 MessageSource
        MessageSource messageSource = createMessageSource();
        validator.validate(user,errors);
        for (ObjectError error : errors.getAllErrors()) {
            String message = messageSource.getMessage(error.getCode(),error.getArguments(), Locale.getDefault());
            System.out.println(message);
        }
    }

    static class UserValidator implements Validator {

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

        @Override
        public void validate(Object target, Errors errors) {
//            User user = (User) target;
//            if(StringUtils.isEmpty(user.getName())){
//                errors.rejectValue("name","name.required");
//            }
            ValidationUtils.rejectIfEmptyOrWhitespace(errors,"name","name.required");
            ValidationUtils.rejectIfEmptyOrWhitespace(errors,"id","id.required");
        }
    }
}

执行结果:

user 对象是否被 UserValidator 支持:true
the name of User must not be null
the id of User must not be null

5.Validator 的救赎

从上面的例子中可以看出 Spring 的 Validator 接口是比较难用的,既需要效验对象本身,还要传递 Errors 对象,以及国际化文案相关的关联。所以 Spring 结合 Bean Validation 来进行适配,以解决这些问题。

5.1 Bean Validation 与 Validator 适配

  • 核心组件 - org.springframework.validation.beanvalidation.LocalValidatorFactoryBean
  • 依赖 Bean Validation - JSR-303 or JSR-349 provider
  • Bean 方法参数效验 - org.springframework.validation.beanvalidation.MethodValidationPostProcessor

5.2 示例

resources/META-INF 目录下新建 bean-validation-context.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!--<context:component-scan base-package="com"/>-->
    <!--<context:annotation-config/>-->
    <context:component-scan base-package="com.huajie.thinking.in.spring.validation"/>

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

    </bean>

    <bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
        <property name="validator" ref="validator"/>
    </bean>

</beans>

示例类

public class SpringBeanValidationDemo {
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:/META-INF/bean-validation-context.xml");

        applicationContext.refresh();

        Validator bean = applicationContext.getBean(Validator.class);
        // System.out.println(bean);

        UserProcessor userConfiguration = applicationContext.getBean(UserProcessor.class);
        User user = new User();
        userConfiguration.processor(user);

        applicationContext.close();
    }

    @Component
    @Validated
    static class UserProcessor {

        public void processor(@Valid User user) {
            System.out.println(user);
        }

    }

    static class User {

        @NotNull
        private String name;

        public String getName() {
            return name;
        }

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

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
}

执行结果:

Exception in thread "main" javax.validation.ConstraintViolationException: processor.user.name: 不能为null
...

6.面试

6.1 Spring 效验接口是哪个?

SpringFramework 中的是 org.springframework.validation.Validator

但是我们一般使用 Spring Bean Validation,也就是 Spring Validator 和 Bean Validation 整合之后的使用方式。

Bean Validation 的实现一般用 hibernate-validator 的实现版本。

6.2 Spring 有哪些效验核心组件?

  • 效验器:org.springframework.validation.Validator
  • 错误收集器:org.springframework.validation.Errors
  • Java Bean 错误描述:org.springframework.validation.ObjectError
  • Java Bean 属性错误描述:org.springframework.validation.FieldError
  • Bean Validation 适配:org.springframework.validation.beanvalidation.LocalValidatorFactoryBean

7.参考

  • 极客时间-小马哥《小马哥讲Spring核心编程思想》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章