前言
浩浩荡荡的把一般程序员都不太关注的Bean Validation话题讲了这么久,期间小伙伴wx我说一直还没看到他最想看到的内容,我问最想看到啥?他说显然是数据校验在Spring中的使用啊。我想若不出意外,这应该是众多小伙伴的共同心声吧,但路漫漫其修远兮,也得上下求索,本文将切入到最关心的Spring中来~
要想深入了解Spring对Bean Validation的支持,org.springframework.validation.beanvalidation这个包里面的这几个关键API必须搞明白喽,这样再使用起@Valid结合Spring时时才能更加的收放自如~
说明:这个包所在的jar是spring-context,属于Spring上下文的核心功能模块
我把这个包内的类图截图如下,供以参考
Spring虽然没有直接实现Bean校验这块的JSR规范,但是从Spring3.0开始,Spring就提供了对Bean Validation的支持。
- 3.0提供了Bean级别的校验
- 3.1提供了更加强大的方法级别的校验
BeanValidationPostProcessor
它就是个普通的BeanPostProcessor。它能够去校验Spring容器中的Bean,从而决定允不允许它初始化完成。
比如我们有些Bean某些字段是不允许为空的,比如数据的链接,用户名密码等等,这个时候用上它处理就非常的优雅和高级了~
若校验不通过,在违反约束的情况下就会抛出异常,阻止容器的正常启动~
public class BeanValidationPostProcessor implements BeanPostProcessor, InitializingBean { // 这就是我们熟悉的校验器 // 请注意这里是javax.validation.Validator,而不是org.springframework.validation.Validator @Nullable private Validator validator; // true:表示在Bean初始化之后完成校验 // false:表示在Bean初始化之前就校验 private boolean afterInitialization = false; ... // 省略get/set // 由此可见使用的是默认的校验器(当然还是Hibernate的) @Override public void afterPropertiesSet() { if (this.validator == null) { this.validator = Validation.buildDefaultValidatorFactory().getValidator(); } } // 这个实现太简单了~~~ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (!this.afterInitialization) { doValidate(bean); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (this.afterInitialization) { doValidate(bean); } return bean; } protected void doValidate(Object bean) { Assert.state(this.validator != null, "No Validator set"); Object objectToValidate = AopProxyUtils.getSingletonTarget(bean); if (objectToValidate == null) { objectToValidate = bean; } Set<ConstraintViolation<Object>> result = this.validator.validate(objectToValidate); // 拼接错误消息最终抛出 if (!result.isEmpty()) { StringBuilder sb = new StringBuilder("Bean state is invalid: "); for (Iterator<ConstraintViolation<Object>> it = result.iterator(); it.hasNext();) { ConstraintViolation<Object> violation = it.next(); sb.append(violation.getPropertyPath()).append(" - ").append(violation.getMessage()); if (it.hasNext()) { sb.append("; "); } } throw new BeanInitializationException(sb.toString()); } } }
这个BeanValidationPostProcessor实现的功能确实非常的简单,无非就是对所有的Bean在初始化前/后进行校验。
我们若是对Spring Bean想做约束的话(比如对属性、构造器等等),使用它就非常的方便~
备注:BeanValidationPostProcessor默认可是没有被装配进容器的~
org.springframework.validation.Validator
应用程序特定对象的验证器,这是Spring自己的抽象,注意区别于javax.validation.Validator。这个接口完全脱离了任何基础设施或上下文,也就是说,它没有耦合到只验证Web层、数据访问层或任何层中的对象。它支持应用于程序内的任何层
// 注意:它可不是Spring3后才推出的 最初就有 public interface Validator { // 此clazz是否可以被validate boolean supports(Class<?> clazz); // 执行校验,错误消息放在Errors 装着 // 可以参考ValidationUtils这个工具类,它能帮助你很多 void validate(Object target, Errors errors); }
它的继承树如下:
SmartValidator
这个子接口它扩展增加了校验分组:hints。
// @since 3.1 这个出现得比较晚 public interface SmartValidator extends Validator { // 注意:这里的Hints最终都会被转化到JSR的分组里去~~ // 所以这个可变参数,传接口Class对象即可~ void validate(Object target, Errors errors, Object... validationHints); // @since 5.1 简单的说,这个方法子类请复写 否则不能使用 default void validateValue(Class<?> targetType, String fieldName, @Nullable Object value, Errors errors, Object... validationHints) { throw new IllegalArgumentException("Cannot validate individual value for " + targetType); } }
SpringValidatorAdapter:校验适配器(重要)
这个实现类Class是非常重要的,它是javax.validation.Validator到Spring的Validator的适配,通过它就可以对接到JSR的校验器来完成校验工作了~
在Spring5.0后,此实现类已完美支持到Bean Validation 2.0
// @since 3.0 public class SpringValidatorAdapter implements SmartValidator, javax.validation.Validator { // 通用的三个约束注解都需要有的属性 private static final Set<String> internalAnnotationAttributes = new HashSet<>(4); static { internalAnnotationAttributes.add("message"); internalAnnotationAttributes.add("groups"); internalAnnotationAttributes.add("payload"); } // 最终都是委托给它来完成校验的~~~ @Nullable private javax.validation.Validator targetValidator; public SpringValidatorAdapter(javax.validation.Validator targetValidator) { Assert.notNull(targetValidator, "Target Validator must not be null"); this.targetValidator = targetValidator; } // 简单的说:默认支持校验所有的Bean类型~~~ @Override public boolean supports(Class<?> clazz) { return (this.targetValidator != null); } // processConstraintViolations做的事一句话解释: // 把ConstraintViolations错误消息,全都适配放在Errors(BindingResult)里面存储着 @Override public void validate(Object target, Errors errors) { if (this.targetValidator != null) { processConstraintViolations(this.targetValidator.validate(target), errors); } } @Override public void validate(Object target, Errors errors, Object... validationHints) { if (this.targetValidator != null) { processConstraintViolations(this.targetValidator.validate(target, asValidationGroups(validationHints)), errors); } } @SuppressWarnings("unchecked") @Override public void validateValue(Class<?> targetType, String fieldName, @Nullable Object value, Errors errors, Object... validationHints) { if (this.targetValidator != null) { processConstraintViolations(this.targetValidator.validateValue( (Class) targetType, fieldName, value, asValidationGroups(validationHints)), errors); } } // 把validationHints都转换为group (支识别Class类型哦) private Class<?>[] asValidationGroups(Object... validationHints) { Set<Class<?>> groups = new LinkedHashSet<>(4); for (Object hint : validationHints) { if (hint instanceof Class) { groups.add((Class<?>) hint); } } return ClassUtils.toClassArray(groups); } // 关于Implementation of JSR-303 Validator interface 省略... }
这个适配器它把所有的Spring接口的校验方法,最终都委托给了org.springframework.validation.Validator,这样就可以完美的和JSR结合起来使用了,功能更加的强大~
虽然本类它是个Class实体类,但是一般来说不建议直接使用它