初步认识
spring数据验证核心类:①:Validator ②:Errors,两者之间的纽带是Validator中定义的validate方法。
public interface Validator {// 限定Validator的职责,不可能所有的校验全部交给一个Validator来做boolean supports(Class<?> clazz);// 将target校验错误信息放入Errors中void validate(Object target, Errors errors);
}
简单使用
@Data
public class Customer {private String name;private String sex;private PhoneNumber phoneNumber;
}
@Data
public class PhoneNumber {private String number;private String areaCode;
}
public class CustomerValidator implements Validator {private PhoneNumberValidator phoneNumberValidator;@Overridepublic boolean supports(Class<?> aClass) {return ClassUtils.isAssignable (aClass,Customer.class);}@Overridepublic void validate(Object target, Errors errors) {Customer customer = (Customer) target;// 最后一个参数可以替换掉messages_zh_CN.properties中对应errorCode消息中的占位符ValidationUtils.rejectIfEmpty (errors,"name","name.empty",new Object[]{1,2});ValidationUtils.rejectIfEmpty (errors,"sex","sex.empty");PhoneNumber phoneNumber = customer.getPhoneNumber ();// 这里涉及到嵌套校验,需要改变校验对象的上下文路径// 这个名称并不是随便写的,和你声明的对象名称一致就好了,如果是list,比如List<PhoneNumber> // phoneNumbers ;那么这里就变成循环校验,上下文路径就应该是phoneNumbers[i],i是循环变量,1,2,3...errors.pushNestedPath ("phoneNumber");ValidationUtils.invokeValidator (phoneNumberValidator,phoneNumber,errors);errors.popNestedPath ();}public void setPhoneNumberValidator(PhoneNumberValidator phoneNumberValidator) {this.phoneNumberValidator = phoneNumberValidator;}
}
public class PhoneNumberValidator implements Validator {@Overridepublic boolean supports(Class<?> aClass) {return ClassUtils.isAssignable (aClass,PhoneNumber.class);}@Overridepublic void validate(Object target, Errors errors) {PhoneNumber phoneNumber = (PhoneNumber) target;if(phoneNumber == null)errors.reject ("PhoneNumber is null");if(!StringUtils.isNumeric (phoneNumber.getAreaCode ()))errors.rejectValue ("areaCode","areaCode.numeric","areaCode cannot be empty");if(!StringUtils.isNumeric (phoneNumber.getNumber ()))errors.rejectValue ("number","number.numeric");}
}
public class TestDemo {public static void main(String[] args) {CustomerValidator customerValidator = new CustomerValidator ();customerValidator.setPhoneNumberValidator (new PhoneNumberValidator ());ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource ();messageSource.setBasename ("messages");Customer customer = new Customer ();PhoneNumber phoneNumber = new PhoneNumber ();customer.setPhoneNumber (phoneNumber);BindException errors = new BindException (customer, "customer");ValidationUtils.invokeValidator (customerValidator,customer,errors);if(errors.hasErrors ()){List<ObjectError> allErrors = errors.getAllErrors ();for (int i = 0; i < allErrors.size (); i++) {String message = messageSource.getMessage (allErrors.get (i).getCode (), allErrors.get (i).getArguments (), Locale.CHINA);System.out.println (message);}}}
}
当然在spring中,我们没有必要自己去定义Validator,它给我们提供了相关的实现类LocalValidatorFactoryBean。
但是它并未帮助我们实现校验的相关逻辑,想想也不可能嘛,它咋知道我们想要怎样的校验,于是它委托其他类来实现,同时我们也不需要自己绑定消息源了,我们只要告诉消息源的位置就可以了。
这里委托校验的类很关键,而已知的hibernate校验框架就可以实现这个功能,它内置了很多的约束校验器,我们只需通过注解就能完成对应的校验功能,当然也可以自定义,这个放在后面讲。
springMVC中我们可以这么配置:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"><property name="providerClass" value="org.hibernate.validator.HibernateValidator"/><property name=''validationMessageSource'' value=""/>
</bean>
其实我们在pom.xml中引入了hibernate-validator的jar包后,provideClass这个属性我们可以不用配置,LocalValidatorFactoryBean在初始化的时候,如果发现provideClass未配置,会去jar包中找。所以如果使用springBoot,我们完全可以不用进行配置,当然如果需要指定消息源,则需要进行相关配置了。
@Configuration
public class CustomConfiguration {@Beanpublic MessageSource messageSource() {ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();messageSource.setBasenames("classpath:/messages");messageSource.setUseCodeAsDefaultMessage(false);messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1));messageSource.setFallbackToSystemLocale(false);return messageSource;}@Beanpublic LocalValidatorFactoryBean validator() {LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();factoryBean.setValidationMessageSource(messageSource());return factoryBean;}
}
自定义约束校验器
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Constraint(validatedBy = NotXListValidator.class)
public @interface NotXList {String message() default "Should not be X";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}
public class NotXListValidator implements ConstraintValidator<NotXList, List<String>> {@Overridepublic void initialize(NotXList constraintAnnotation) {}@Overridepublic boolean isValid(List<String> list, ConstraintValidatorContext context) {boolean valid = true;for (int i = 0; i < list.size(); i++) {if ("X".equals(list.get(i))) {valid = false;}}return valid;}
}
public class ListContainer {@NotXListprivate List<String> list = new LinkedList<>();public void addString(String value) {list.add(value);}public List<String> getList() {return list;}public static void main(String[] args) {LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();validator.afterPropertiesSet();ListContainer listContainer = new ListContainer();listContainer.addString("A");listContainer.addString("X");BeanPropertyBindingResult errors = new BeanPropertyBindingResult(listContainer, "listContainer");validator.validate(listContainer, errors);}
}
一些简单的校验器Hibernate校验框架已经提供了,大家可以参考,我用的6.0.20的包,都位于org.hibernate.validator.internal.constraintvalidators.bv包下面。
请求参数的校验
// 使用@Valid或者@Validated将会对请求参数进行校验
@Controller
@RequestMapping("/test")
public class ValidationTestController {@ResponseBody@RequestMapping("/test1")public String test1(@Valid User user){return "test1";}
}
原理:请求参数在解析的过程中会进行校验,下面是校验的逻辑
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {Annotation[] annotations = parameter.getParameterAnnotations();for (Annotation ann : annotations) {Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});binder.validate(validationHints);break;}}}
方法校验
public interface MyValidInterface<T> {@NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2);T myGenericMethod(@NotNull T value);
}
// 这个注解一定要有,不然不起作用
@Validated
@Service
public class MyValidBean implements MyValidInterface<String> {@Overridepublic Object myValidMethod(String arg1, int arg2) {return (arg2 == 0 ? null : "value");}@Overridepublic String myGenericMethod(String value) {return value;}
}
springBoot中
@Controller
public class ValidController{@Autowiredprivate MyValidBean validBean;@RequestBody@RequestMapping("/valid")public String testValid(String type,int age){// 这里如果参数不满足指定方法的校验要求,就会报错return validBean.myValidMethod(type,age);}
}
spring中需要配置MethodValidationPostProcessor
<bean id="methodValidationPostProcessor" class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
</bean>
MethodValidationPostProcessor的实现原理就是定义了一个切面,这有关springAOP的内容,在这篇博客里面我不打算深入讲解。
有关Validator的内容暂时先写到这里,以后有内容需要补充再记录。