文章目录
夯实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核心编程思想》