一 概要流程
流程
- Spring MVC 主框架将
ServletRequest
对象及目标方法的入参实例传递给WebDataBinderFactory
实例,以创建DataBinder
实例对象 - DataBinder 调用装配在 Spring MVC 上下文中的
ConversionService 组件
进行数据类型转换、数据格式化工作。将 Servlet 中的请求信息填充到入参对象中 - 调用
Validator
组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果BindingData
对象 - Spring MVC 抽取
BindingResult
中的入参对象
和校验错误对象
,将它们赋给处理方法的响应入参
Spring MVC 通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是 DataBinder,运行机制如下:
原理
- DataBinder负责将请求的参数绑定到目标方法的参数中去(有些是pojo)
- DataBinder中有几个组件负责完成这几项工作;
- ConversionService负责进行
类型转换
以及格式化
工作 - 如果有数据校验使用校验器进行 validators,是一个集合。可以有多个校验器
- 处理校验成功还是失败信息?BindingResult负责处理校验错误的信息
二 扩展
自定义的类型转换器
对一些时间类型,或者特殊的POJO,我们可以定义自己的类型转换器
,然后放到ConversionService 中。
Spring 定义了 3 种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到 ConversionServiceFactoryBean 中:
- Converter<S,T>:将 S 类型对象转为 T 类型对象
- ConverterFactory:将相同系列多个 “同质” Converter 封装在一起。如果希望将一种类型的对象转换为另一种类型及其子类的对象(例如将 String 转换为 Number 及 Number 子类(Integer、Long、Double 等)对象)可使用该转换器工厂类
- GenericConverter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换
数据校验
helloworld
@Controller
@RequestMapping("/valid")
public class ValidController {
@RequestMapping("/v1")
@ResponseBody
public String v1(@Valid Student student, BindingResult result){
System.out.println("v1");
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
return "v1";
}
}
实体类
public class Student {
private int id;
@NotNull
@Length(min=2,max=4,message="姓名格式不正确")
private String name;
@Length(min=4,max=5,message = "地址长度错误")
private String address;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
校验模式
- 普通模式(默认是这个模式)
普通模式(会校验完所有的属性,然后返回所有的验证失败信息)
2.快速失败返回模式
快速失败返回模式(只要有一个验证失败,则返回)
failFast:true 快速失败返回模式 false 普通模式
两种配置方式
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.failFast( true )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
===
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.addProperty( "hibernate.validator.fail_fast", "true" )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
配置方式
@Configuration
public class ValidatorConfiguration {
@Bean
public Validator validator(){
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.addProperty( "hibernate.validator.fail_fast", "true" )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
}
如果有多个参数要校验,就需要多个接收多个BindingResult 对象
public void test()(@RequestBody @Valid DemoModel demo, BindingResult result)
public void test()(@RequestBody @Valid DemoModel demo, BindingResult result,@RequestBody @Valid DemoModel demo2, BindingResult result2)
校验@RequestParam参数
使用校验bean
的方式,没有办法校验RequestParam
的内容
使用@Valid注解,对RequestParam对应的参数进行注解,是无效的,需要使用@Validated注解来使得验证生效。如下所示:
- 添加
MethodValidationPostProcessor
bena
@Configuration
public class ValidConbfiguration {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
/**设置validator模式为快速失败返回*/
postProcessor.setValidator(validator());
return postProcessor;
}
@Bean
public Validator validator(){
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.addProperty( "hibernate.validator.fail_fast", "true" )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
- Controller上加注解@Validated
@Controller
@RequestMapping("/valid")
@Validated
public class ValidController {
- 可以看到:验证不通过时,抛出了ConstraintViolationException异常,使用同一捕获异常处理:
@ControllerAdvice
@Component
public class GlobalExceptionHandler {
@ExceptionHandler
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handle(ValidationException exception) {
if(exception instanceof ConstraintViolationException){
ConstraintViolationException exs = (ConstraintViolationException) exception;
Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
for (ConstraintViolation<?> item : violations) {
/**打印验证不通过的信息*/
System.out.println("global: "+item.getMessage());
}
}
return "bad request, " ;
}
}
model校验
这里我没有对类加@Data
@Autowired
private Validator validator;
@RequestMapping("/demo3")
@ResponseBody
public String demo3(String name){
Student s = new Student();
s.setName(name);
Set<ConstraintViolation<Student>> violationSet = validator.validate(s);
for (ConstraintViolation<Student> model : violationSet) {
System.out.println(model.getMessage());
}
return "demo3";
}
对象级联校验
对级联的属性加上@Valid
public class Student {
private int id;
@NotNull
@Length(min=2,max=4,message="姓名格式不正确")
private String name;
@Length(min=4,max=5,message = "地址长度错误")
private String address;
@Valid
private Student friend;
...
分组校验
详见参考引用
网友评论