夯實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核心編程思想》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章