文章目錄
夯實Spring系列|第十八章:Spring Validation 效驗
本章說明
本章內容有和 第十七章:Spring 國際化(i18n) 以及下一章 [Spring 數據綁定] 都有緊密關聯
1.項目環境
- jdk 1.8
- spring 5.2.2.RELEASE
- github 地址:https://github.com/huajiexiewenfeng/thinking-in-spring
- 本章模塊:validation
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 結論
-
從調試點可以看出 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 獲得最終的文案
- 通過 Errors 對象收集錯誤
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核心編程思想》