在Java数据校验详解中详细介绍了Java数据校验相关的功能(简称Bean Validation,涵盖JSR-303、JSR-349、JSR-380),本文将在Bean Validation的基础上介绍Spring框架提供的数据校验功能。
Spring提供的数据校验功能分为2个部分,一个是Spring自定义的数据校验功能(以下称为Spring Validation),一个是符合Bean Validation规范的数据校验功能。
Spring Validation数据校验
Spring的自行开发的数据校验功能由3个部分组成:
校验器——Validator,他会运行校验代码。
校验对象,实际上就是一个JavaBean,Validator会对其进行校验。
校验结果——Errors,一次校验的结果都存放在Errors实例中。
这是Spring在Bean Validation规范制定之前就实现的数据校验功能,ValidationUtils的注释中@since标签是2003年5月6号,而JSR-303定稿时间已经是6年之后(2009年)的事了。
Spring的数据校验功能就是实现检验器、校验对象、校验结果三个对象。先声明个一个校验对象(实体):
packagechkui.springcore.example.hybrid.springvalidation.entity;//车辆信息publicclassVehicle{privateString name;privateString type;privateString engine;privateString manufacturer;privateCalendar productionDate;/**Getter Setter*/}
然后针对这个实体声明一个校验器。校验器要实现org.springframework.validation.Validator接口:
packagechkui.springcore.example.hybrid.springvalidation.validator;publicclassVehicleValidatorimplementsValidator{privateList _TYPE = Arrays.asList(newString[] {"CAR","SUV","MPV"});publicbooleansupports(Class<?> clazz){//将验证器和实体类进行绑定,如果这里返回false在验证过程中会抛出类型不匹配的异常returnVehicle.class.isAssignableFrom(clazz);}publicvoidvalidate(Object target, Errors errors){//验证数据Vehicle vehicle = Vehicle.class.cast(target);if(null== vehicle.getName()) {//使用验证工具绑定结果ValidationUtils.rejectIfEmpty(errors,"name","name.empty","车辆名称为空");}if(!_TYPE.contains(vehicle.getType())) {//向Error添加验证错误信息<2> errors.rejectValue("type","type.error","汽车类型必须是"+ _TYPE);}//More validate ......}}
有了验证对象(JavaBean)和对应的验证器(Validator)就完成了一组验证功能。注意VehicleValidator::validate方法传递的errors参数,验证工具会将错误实例传递进来交给开发者去组装验证结果。
代码中的ValidationUtils就是数据校验工具,他提供了2个功能:
执行校验(接下来会马上介绍)。
提供错误信息绑定的功能,例如ValidationUtils.rejectIfEmpty这一行代码。会将对应的信息写入到Errors中。
有了验证对象和验证器就可以执行验证:
publicclassSpringValidationApp{privatestaticvoidspringValidation(ApplicationContext ctx){VehicleValidator vehicleValidator =newVehicleValidator();//创建验证器Vehicle vehicle =newVehicle();//创建验证对象<1> ValidationError error =newValidationError("Vehicle");//创建错误信息ValidationUtils.invokeValidator(vehicleValidator, vehicle, error);//执行验证List list = error.getFieldErrors();intcount =1;//输出验证结果for(FieldError res : list) {print("Error Info ", count++ ,".");print("Entity:", res.getObjectName());print("Field:", res.getField());print("Code:", res.getCode());print("Message:", res.getDefaultMessage());print("-");}}}
执行完毕后,ValidationError中记录了所有校验错误信息。错误信息分为4个部分:
验证的对象的名称:在执行验证器的代码中<1>部分创建错误对象时指定。Vehicle就是验证对象的名称。
错误的域、错误code和错误信息:每一个错误都有对应的域、错误编码以及错误信息,在验证器<2>位置的代码就是指定错误信息。
以上错误信息可以通过error.getFieldErrors();来获取。
如果JavaBean有嵌套的结构,可以在校验器中调用其他的校验器来实现嵌套检验。先为Vehicle类增加一个Gearbox(变速箱)域:
packagechkui.springcore.example.hybrid.springvalidation.entity;//车辆信息publicclassVehicle{privateString name;privateString type;privateString engine;privateString manufacturer;privateGearbox gearbox;//Gearbox是另外一个实例privateCalendar productionDate;/**Getter Setter*/}
//变速箱publicclassGearbox{privateString name;privateString manufacturer;/**Getter Setter*/}
在校验器VehicleValidator::validate中增加对Gearbox验证:
publicclassVehicleValidatorimplementsValidator{@AutowiredGearboxValidator gearboxValidator;//用于校验Gearbox的校验器@Overridepublicvoidvalidate(Object target, Errors errors){Vehicle vehicle = Vehicle.class.cast(target);//some code ......}if(null== vehicle.getGearbox()) {errors.rejectValue("gearbox","gearbox.error","变速箱信息为空");}else{//指定子实体的名称errors.pushNestedPath("gearbox");//执行对Gearbox的校验ValidationUtils.invokeValidator(gearboxValidator, vehicle.getGearbox(), errors);}}}
Bean Validation数据校验
Spring现在推荐使用Bean Validation来进行数据校验,而且已经整合到Spring MVC框架中。
在Spring中使用Bean Validation和Java数据校验详解一文中介绍的内容差不多——也是注解和校验器组成一个约束,通过注解来控制校验的过程。
Spring核心部分没有提供Bean Validation相关的实现类,所以需要引入对应的实现框架。本文引入的是Hibernate Validator,他包括验证器和el,详情可以看源码根目录的build.gradle文件。
首先我们向IoC容器中添加全局校验器:
@ConfigurationpublicclassSpringValidationConfig{@Bean("validator")publicValidatorvalidator(){returnnewLocalValidatorFactoryBean();}
这一段添加Bean的代码非常简单,就是新建了一个LocalValidatorFactoryBean实例。LocalValidatorFactoryBean实现了javax.validation.Validator接口,并且会自动使用已经引入的Bean Validation框架。
然后向Vehicle增加Bean Validation相关的注解:
publicclassVehicle{@NotBlankprivateString name;@NotBlank@VehicleTypeprivateString type;@NotBlankprivateString engine;@NotBlankprivateString manufacturer;<3>@Valid//@Valid的作用是对嵌套的解构进行校验privateGearbox gearbox;@ValidprivateTyre tyre;@VehicleProductionDateprivateCalendar productionDate;/**Getter Setter*/}
在上面的代码中,除了常规的@NotBlank等注解,还有@VehicleType这个自定义注解。在代码<3>的位置@Valid是告诉校验器还要对gearbox的实例进行校验,相当于前面介绍的嵌套校验功能。最后我们使用检验器来对Vehicle的实例进行校验:
publicclassSpringValidationApp{publicstaticvoidmain(String[] args){ApplicationContext ctx =newAnnotationConfigApplicationContext(SpringValidationConfig.class);BeanValidation(ctx);//JSR规范验证}privatestaticvoidBeanValidation(ApplicationContext ctx){Validator validator = ctx.getBean(Validator.class);//获取校验器Vehicle vehicle =newVehicle();//新建要校验的对象validator.validate(vehicle).forEach(err -> {//执行校验print("Field: ", err.getPropertyPath());print("Error: ", err.getMessage());});}}
关于Bean Validation的详细使用方法已经在Java数据校验详解介绍。
兼容Bean Validation和Spring Validation
一些相对比较久远的项目可能会遇见在Spring Validation的基础上新增Bean Validation功能的情况。可以使用SpringValidatorAdapter适配器来解决这个问题:
publicclassSpringValidationApp{privatestaticvoidadapterValidation(ApplicationContext ctx){// 获取校验器// LocalValidatorFactoryBean继承了SpringValidatorAdapter// 所以这里就是获取LocalValidatorFactoryBeanSpringValidatorAdapter adapter = ctx.getBean(SpringValidatorAdapter.class);Vehicle vehicle =newVehicle();// 检验对象ValidationError error =newValidationError("Vehicle");// Spring ValidationValidationUtils.invokeValidator(adapter, vehicle, error);//执行校验List list = error.getFieldErrors();//检验信息// Bean Validation 校验adapter.validate(vehicle).forEach(err -> {// 执行检验&输出校验结果print("Field: ", err.getPropertyPath());print("Error: ", err.getMessage());});}}
上面的代码使用SpringValidatorAdapter分别执行了Bean Validation和Spring Validation。可以将SpringValidatorAdapter看作一个org.springframework.validation.Validator的实现类用ValidationUtils来执行校验,而验证的过程完全是按照Bean Validation的规范来执行的。
方法参数校验
除了校验一个实体类,Spring在Bean Validation的基础上使用后置处理器和AOP实现了方法参数的检验。例如下面的方法:
publicinterfacePersonService{public@NotBlankStringexecute(@NotBlank(message ="必须设置人员名称")String name,@Min(value =18, message ="年龄必须大于18")intage);}
他表示返回数据不能为空字符串,传入的2个参数name不能为空字符串、age必须大于18。
要启用方法参数校验关键点是引入MethodValidationPostProcessor并在需要验证的Bean上增加一个@Validated注解。
先通过@Configuration引入后置处理器:
@Configuration@ComponentScan("chkui.springcore.example.hybrid.springvalidation.service")publicclassSpringValidationConfig{@Bean("validator")publicValidatorvalidator(){returnnewLocalValidatorFactoryBean();}@BeanpublicMethodValidationPostProcessormethodValidationPostProcessor(Validator validator){MethodValidationPostProcessor postProcessor =newMethodValidationPostProcessor();postProcessor.setValidator(validator);returnpostProcessor;}}
然后实现上面的PersonService接口并标记@Validated表示这个类中的方法要进行参数校验:
@Service@ValidatedpublicclassPersonServiceImplimplementsPersonService{@OverridepublicStringexecute(String name,intage){return"I'm "+ name +". "+ age +" years old.";}}
最后使用这个Service:
publicclassSpringValidationApp{publicstaticvoidmain(String[] args){ApplicationContext ctx =newAnnotationConfigApplicationContext(SpringValidationConfig.class);methodValidation(ctx);//方法参数校验}privatestaticvoidmethodValidation(ApplicationContext ctx){//对方法进行参数校验try{PersonService personService = ctx.getBean(PersonService.class);personService.execute(null,1);//传递参数}catch(ConstraintViolationException error) {error.getConstraintViolations().forEach(err -> {//输出校验错误信息print("Field: ", err.getPropertyPath());print("Error: ", err.getMessage());});}}}
在运行的过程中,如果参数或返回数据不符合验证规则会抛出ConstraintViolationException异常,可以从中获取校验错误的信息。
欢迎工作一到五年的Java工程师朋友们加入Java技术交流群:659270626
群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!
c 9����Wj