一 概要流程
流程
- Spring MVC 主框架將
ServletRequest對(duì)象及目標(biāo)方法的入?yún)?shí)例傳遞給WebDataBinderFactory實(shí)例,以創(chuàng)建DataBinder實(shí)例對(duì)象 - DataBinder 調(diào)用裝配在 Spring MVC 上下文中的
ConversionService 組件進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換、數(shù)據(jù)格式化工作。將 Servlet 中的請(qǐng)求信息填充到入?yún)?duì)象中 - 調(diào)用
Validator組件對(duì)已經(jīng)綁定了請(qǐng)求消息的入?yún)?duì)象進(jìn)行數(shù)據(jù)合法性校驗(yàn),并最終生成數(shù)據(jù)綁定結(jié)果BindingData對(duì)象 - Spring MVC 抽取
BindingResult中的入?yún)?duì)象和校驗(yàn)錯(cuò)誤對(duì)象,將它們賦給處理方法的響應(yīng)入?yún)?/li>
Spring MVC 通過(guò)反射機(jī)制對(duì)目標(biāo)處理方法進(jìn)行解析,將請(qǐng)求消息綁定到處理方法的入?yún)⒅?。?shù)據(jù)綁定的核心部件是 DataBinder,運(yùn)行機(jī)制如下:

image.png
原理
- DataBinder負(fù)責(zé)將請(qǐng)求的參數(shù)綁定到目標(biāo)方法的參數(shù)中去(有些是pojo)
- DataBinder中有幾個(gè)組件負(fù)責(zé)完成這幾項(xiàng)工作;
- ConversionService負(fù)責(zé)進(jìn)行
類型轉(zhuǎn)換以及格式化工作 - 如果有數(shù)據(jù)校驗(yàn)使用校驗(yàn)器進(jìn)行 validators,是一個(gè)集合??梢杂卸鄠€(gè)校驗(yàn)器
- 處理校驗(yàn)成功還是失敗信息?BindingResult負(fù)責(zé)處理校驗(yàn)錯(cuò)誤的信息
二 擴(kuò)展
自定義的類型轉(zhuǎn)換器
對(duì)一些時(shí)間類型,或者特殊的POJO,我們可以定義自己的類型轉(zhuǎn)換器,然后放到ConversionService 中。
Spring 定義了 3 種類型的轉(zhuǎn)換器接口,實(shí)現(xiàn)任意一個(gè)轉(zhuǎn)換器接口都可以作為自定義轉(zhuǎn)換器注冊(cè)到 ConversionServiceFactoryBean 中:
- Converter<S,T>:將 S 類型對(duì)象轉(zhuǎn)為 T 類型對(duì)象
- ConverterFactory:將相同系列多個(gè) “同質(zhì)” Converter 封裝在一起。如果希望將一種類型的對(duì)象轉(zhuǎn)換為另一種類型及其子類的對(duì)象(例如將 String 轉(zhuǎn)換為 Number 及 Number 子類(Integer、Long、Double 等)對(duì)象)可使用該轉(zhuǎn)換器工廠類
- GenericConverter:會(huì)根據(jù)源類對(duì)象及目標(biāo)類對(duì)象所在的宿主類中的上下文信息進(jìn)行類型轉(zhuǎn)換
數(shù)據(jù)校驗(yàn)
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";
}
}
實(shí)體類
public class Student {
private int id;
@NotNull
@Length(min=2,max=4,message="姓名格式不正確")
private String name;
@Length(min=4,max=5,message = "地址長(zhǎng)度錯(cuò)誤")
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;
}
}
校驗(yàn)?zāi)J?/h3>
- 普通模式(默認(rèn)是這個(gè)模式)
普通模式(會(huì)校驗(yàn)完所有的屬性,然后返回所有的驗(yàn)證失敗信息)
2.快速失敗返回模式
快速失敗返回模式(只要有一個(gè)驗(yàn)證失敗,則返回)
普通模式(會(huì)校驗(yàn)完所有的屬性,然后返回所有的驗(yàn)證失敗信息)
2.快速失敗返回模式
快速失敗返回模式(只要有一個(gè)驗(yàn)證失敗,則返回)
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;
}
}
如果有多個(gè)參數(shù)要校驗(yàn),就需要多個(gè)接收多個(gè)BindingResult 對(duì)象
public void test()(@RequestBody @Valid DemoModel demo, BindingResult result)
public void test()(@RequestBody @Valid DemoModel demo, BindingResult result,@RequestBody @Valid DemoModel demo2, BindingResult result2)
校驗(yàn)@RequestParam參數(shù)
使用校驗(yàn)bean的方式,沒(méi)有辦法校驗(yàn)RequestParam的內(nèi)容
使用@Valid注解,對(duì)RequestParam對(duì)應(yīng)的參數(shù)進(jìn)行注解,是無(wú)效的,需要使用@Validated注解來(lái)使得驗(yàn)證生效。如下所示:
- 添加
MethodValidationPostProcessorbena
@Configuration
public class ValidConbfiguration {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
/**設(shè)置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 {
- 可以看到:驗(yàn)證不通過(guò)時(shí),拋出了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) {
/**打印驗(yàn)證不通過(guò)的信息*/
System.out.println("global: "+item.getMessage());
}
}
return "bad request, " ;
}
}
model校驗(yàn)
這里我沒(méi)有對(duì)類加@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";
}
對(duì)象級(jí)聯(lián)校驗(yàn)
對(duì)級(jí)聯(lián)的屬性加上@Valid
public class Student {
private int id;
@NotNull
@Length(min=2,max=4,message="姓名格式不正確")
private String name;
@Length(min=4,max=5,message = "地址長(zhǎng)度錯(cuò)誤")
private String address;
@Valid
private Student friend;
...
分組校驗(yàn)
詳見(jiàn)參考引用