详述Spring对Bean Validation支持的核心API:Validator、SmartValidator、LocalValidatorFactoryBean...【享学Spring】(上)

简介: 详述Spring对Bean Validation支持的核心API:Validator、SmartValidator、LocalValidatorFactoryBean...【享学Spring】(上)

前言


浩浩荡荡的把一般程序员都不太关注的Bean Validation话题讲了这么久,期间小伙伴wx我说一直还没看到他最想看到的内容,我问最想看到啥?他说显然是数据校验在Spring中的使用啊。我想若不出意外,这应该是众多小伙伴的共同心声吧,但路漫漫其修远兮,也得上下求索,本文将切入到最关心的Spring中来~


要想深入了解Spring对Bean Validation的支持,org.springframework.validation.beanvalidation这个包里面的这几个关键API必须搞明白喽,这样再使用起@Valid结合Spring时时才能更加的收放自如~


说明:这个包所在的jar是spring-context,属于Spring上下文的核心功能模块


我把这个包内的类图截图如下,供以参考


image.png


Spring虽然没有直接实现Bean校验这块的JSR规范,但是从Spring3.0开始,Spring就提供了对Bean Validation的支持。


  1. 3.0提供了Bean级别的校验
  2. 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);
}


它的继承树如下:

image.png


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实体类,但是一般来说不建议直接使用它


相关文章
|
5天前
|
Java uml Spring
手写spring第四章-完善bean实例化,自动填充成员属性
手写spring第四章-完善bean实例化,自动填充成员属性
15 0
|
2天前
|
消息中间件 安全 Java
在Spring Bean中,如何通过Java配置类定义Bean?
【4月更文挑战第30天】在Spring Bean中,如何通过Java配置类定义Bean?
8 1
|
4天前
|
前端开发 Java 数据格式
【Spring系列笔记】定义Bean的方式
在Spring Boot应用程序中,定义Bean是非常常见的操作,它是构建应用程序的基础。Spring Boot提供了多种方式来定义Bean,每种方式都有其适用的场景和优势。
19 2
|
5天前
|
XML Java 数据格式
手写spring第七章-完成便捷实现bean对象初始化和销毁方法
手写spring第七章-完成便捷实现bean对象初始化和销毁方法
6 0
|
5天前
|
XML Java 数据格式
手写spring第六章-实现应用上下文,完成bean的扩展机制
手写spring第六章-实现应用上下文,完成bean的扩展机制
11 0
|
5天前
|
设计模式 搜索推荐 Java
手写spring第三章-重构,使用依赖关系完善实例化bean操作
手写spring第三章-重构,使用依赖关系完善实例化bean操作
12 0
|
7天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
JSON Java API
Spring API 开发简单示例及技巧
目录.png 以用户登录为栗子,示例API请求时处理技巧,和API返回数据时如何处理。 一、API返回时:返回的json数据 使用spring mvc默认配置就可以返回json了,不过需要jackson的jar包APIResponse是一...
1065 0
|
2月前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
46 0
|
2月前
|
缓存 安全 Java
Spring Boot 面试题及答案整理,最新面试题
Spring Boot 面试题及答案整理,最新面试题
122 0