深入了解数据校验(Bean Validation):ValidatorFactory和Validator等核心API【享学Java】(上)

简介: 深入了解数据校验(Bean Validation):ValidatorFactory和Validator等核心API【享学Java】(上)

前言


上篇文章 已经介绍了Bean Validation它的概念、JSR标准,也已经感受了一把使用它来对JavaBean进行校验。本文将继续讲解它的余下执行过程~


在这里先说一句,因为Bean Validation涉及到的API关键类实在是太多了(感叹:hibernate validation实现这一套复杂度非常之高),为此我专门写了一个关键类打点篇,若不熟悉关键组件的,本人强烈建议先花几分钟去浏览一下:深入了解数据校验(Bean Validation):基础类打点(ValidationProvider、ConstraintDescriptor、ConstraintValidator)【享学Java】


上文讲到已经通过配置拿到了ValidatorFactory,本文接着此处继续。

说明:下面的讲解基于此案例:

@Getter
@Setter
@ToString
public class Person {
    // 错误消息message是可以自定义的
    @NotNull(message = "{message} -> 名字不能为null")
    public String name;
    @Max(10)
    @Positive
    public Integer age;
    @NotNull
    @NotEmpty
    private List<@Email String> emails;
    @Future
    private Date start;
}
    public static void main(String[] args) {
        Person person = new Person();
        //person.setName("fsx");
        person.setAge(18);
        // email校验:虽然是List都可以校验哦
        person.setEmails(Arrays.asList("fsx@gmail.com", "baidu@baidu.com", "aaa.com"));
        //person.setStart(new Date()); //start 需要是一个将来的时间: Sun Jul 21 10:45:03 CST 2019
        //person.setStart(new Date(System.currentTimeMillis() + 10000)); //校验通过
        HibernateValidatorConfiguration configure = Validation.byProvider(HibernateValidator.class).configure();
        ValidatorFactory validatorFactory = configure.failFast(false).buildValidatorFactory();
        // 根据validatorFactory拿到一个Validator
        Validator validator = validatorFactory.getValidator();
        // 使用validator对结果进行校验
        Set<ConstraintViolation<Person>> result = validator.validate(person);
        // 对结果进行遍历输出
        result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .forEach(System.out::println);
    }


运行打印:

name {message} -> 名字不能为null -> 名字不能为null: null
age 最大不能超过10: 18
emails[2].<list element> 不是一个合法的电子邮件地址: aaa.com


ValidatorFactory:验证器工厂


在准备好了一个javax.validation.Configuration后,接下来最重要的事就是根据配置Configuration拿到一个ValidatorFactory,进而拿到javax.validation.Validator完成校验~


此类从命名上看非常简单:Validator工厂


public interface ValidatorFactory extends AutoCloseable {
  // 显然,这个接口是最为重要的
  Validator getValidator();
  // 定义一个新的ValidatorContext验证器上下文,并且和Validator关联上
  ValidatorContext usingContext();
  // 下面这些获取  不用过多解释了~
  MessageInterpolator getMessageInterpolator();
  TraversableResolver getTraversableResolver();
  ConstraintValidatorFactory getConstraintValidatorFactory();
  ParameterNameProvider getParameterNameProvider();
  ClockProvider getClockProvider();
  public <T> T unwrap(Class<T> type);
  // 复写AutoCloseable的方法
  @Override
  public void close();
}


看看它的继承树:image.png


它的实现主要有Spring的实现和Hibernate Validation的实现。因为Spring后续还有专题非常详细的描述,因此本文就只关注Hibernate的实现了~


HibernateValidatorFactory


它是Hibernate Validation提供的,继承自标准接口ValidatorFactory,在原基础上进行了扩展:对脚本进行支持,以及支持Duration事件误差~

public interface HibernateValidatorFactory extends ValidatorFactory {
  // 它用于支持脚本。支持使用注解@ScriptAssert等(使用太少了)
  // 关于Java运行脚本,可参阅javax.script.ScriptEngineManager
  @Incubating
  ScriptEvaluatorFactory getScriptEvaluatorFactory();
  // 这主要是处理date/time的误差问题~~~
  @Incubating
  Duration getTemporalValidationTolerance();
  // 复写父接口的方法,使用更精确的HibernateValidatorContext验证器上下文
  @Override
  HibernateValidatorContext usingContext();
}

关于唯一实现类:ValidatorFactoryImpl


public class ValidatorFactoryImpl implements HibernateValidatorFactory {
  ... // 省略非常多的成员变量
  // 唯一的构造函数,做了非常非常多初始化的事~~~
  public ValidatorFactoryImpl(ConfigurationState configurationState) {
    ...
  }
  // 这个或许是最重要的一个方法
  @Override
  public Validator getValidator() {
    return createValidator(
        constraintValidatorManager.getDefaultConstraintValidatorFactory(),
        valueExtractorManager,
        validatorFactoryScopedContext,
        methodValidationConfiguration
    );
  }
  Validator createValidator(ConstraintValidatorFactory constraintValidatorFactory, ValueExtractorManager valueExtractorManager,
      ValidatorFactoryScopedContext validatorFactoryScopedContext, MethodValidationConfiguration methodValidationConfiguration) {
    BeanMetaDataManager beanMetaDataManager = beanMetaDataManagers.computeIfAbsent(
        new BeanMetaDataManagerKey( validatorFactoryScopedContext.getParameterNameProvider(), valueExtractorManager, methodValidationConfiguration ),
        key -> new BeanMetaDataManager(
            constraintHelper,
            executableHelper,
            typeResolutionHelper,
            validatorFactoryScopedContext.getParameterNameProvider(),
            valueExtractorManager,
            validationOrderGenerator,
            buildDataProviders(),
            methodValidationConfiguration
        )
     );
    return new ValidatorImpl(
        constraintValidatorFactory,
        beanMetaDataManager,
        valueExtractorManager,
        constraintValidatorManager,
        validationOrderGenerator,
        validatorFactoryScopedContext
    );
  }
  @Override
  public MessageInterpolator getMessageInterpolator() {
    return validatorFactoryScopedContext.getMessageInterpolator();
  }
  @Override
  public TraversableResolver getTraversableResolver() {
    return validatorFactoryScopedContext.getTraversableResolver();
  }
  ...
  @Override
  public <T> T unwrap(Class<T> type) {
    if ( type.isAssignableFrom( HibernateValidatorFactory.class ) ) {
      return type.cast( this );
    }
    throw LOG.getTypeNotSupportedForUnwrappingException( type );
  }
  @Override
  public HibernateValidatorContext usingContext() {
    return new ValidatorContextImpl( this );
  }
  ...
}


此验证器工厂的实现类源码,不可为不复杂(复杂代码本文并没有贴出),我反复看了几遍仍还处于一个准懵逼的状态。

为此我觉得应该关注点,基于打点文章里已经介绍了关键的各个组件,so我不在纠结于实现(创建)细节,只需要着重关注Validator验证器本身了。


说明:Spring对ValidatorFactory的实现稍微简单点,但也不会太容易。因为绝大多数我们在使用Spring,因此在Spring章节此处不会放过~


Validator:验证器


官方的解释简单明了:校验Bean实例~ ,介绍得非常简单但却又是这么回事有木有

到此处,就正式和Bean的校验开始打交道了,也是我们最直接能出效果的一个API,所以说它是最重要的其实也不过为


public interface Validator {
  // 校验作用在此Bean上面的所有约束(所有属性、方法、构造器的所有约束)
  // groups可以指定只使用某个group,默认是Defualt的group嘛~
  <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);
  // 上面太过于粗暴。这里是校验这个Bean上 某个具体的属性~
  <T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?>... groups);
  // 这个就更加精确了,具体的属性的具体value值都要校验
  <T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType, String propertyName, Object value, Class<?>... groups);
  // 返回描述Bean约束的描述符对象,此对象和ConstraintDescriptor关联
  // 并且还持有PropertyDescriptor和ConstructorDescriptor等等~
  BeanDescriptor getConstraintsForClass(Class<?> clazz);
  <T> T unwrap(Class<T> type);
  // 返回用于验证方法和构造函数的参数和返回值的协定。
  // 不巧的是:ValidatorImpl实现了Validator, ExecutableValidator这两个接口
  ExecutableValidator forExecutables();
}
// ================关于ExecutableValidator接口================
// 它用于验证 方法 和 构造函数 的 **参数和返回值**。
public interface ExecutableValidator {
  // 验证方法的入参
  <T> Set<ConstraintViolation<T>> validateParameters(T object, Method method,
                             Object[] parameterValues, Class<?>... groups);
  // 验证方法的返回值
  <T> Set<ConstraintViolation<T>> validateReturnValue(T object, Method method,
                            Object returnValue, Class<?>... groups);
  // 不解释~~~~~~~~~~~~
  <T> Set<ConstraintViolation<T>> validateConstructorParameters(Constructor<? extends T> constructor,
                                  Object[] parameterValues,
                                  Class<?>... groups);
  <T> Set<ConstraintViolation<T>> validateConstructorReturnValue(Constructor<? extends T> constructor,
                                   T createdObject,
                                   Class<?>... groups);
}

image.png


Spring对它的实现非常丰富,但本文还是只看ValidatorImpl。

相关文章
|
1月前
|
JSON Java API
【干货满满】分享京东API接口到手价,用Java语言实现
本示例使用 Java 调用京东开放平台商品价格及优惠信息 API,通过商品详情和促销接口获取到手价(含优惠券、满减等),包含签名生成、HTTP 请求及响应解析逻辑,适用于比价工具、电商系统集成等场景。
|
6月前
|
缓存 监控 负载均衡
如何提升 API 性能:来自 Java 和测试开发者的优化建议
本文探讨了如何优化API响应时间,提升用户体验。通过缓存(如Redis/Memcached)、减少数据负载(REST过滤字段或GraphQL精确请求)、负载均衡(Nginx/AWS等工具)、数据压缩(Gzip/Brotli)、限流节流、监控性能(Apipost/New Relic等工具)、升级基础设施、减少第三方依赖、优化数据库查询及采用异步处理等方式,可显著提高API速度。快速响应的API不仅让用户满意,还能增强应用整体性能。
|
1月前
|
JSON Java API
【干货满满】分享拼多多API接口到手价,用Java语言实现
本方案基于 Java 实现调用拼多多开放平台商品详情 API,通过联盟接口获取商品到手价(含拼团折扣与优惠券),包含签名生成、HTTP 请求及响应解析逻辑,适用于电商比价、导购系统集成。
|
1月前
|
JSON Java API
【干货满满】分享淘宝API接口到手价,用Java语言实现
本文介绍了如何使用 Java 调用淘宝开放平台 API 获取商品到手价,涵盖依赖配置、签名生成、HTTP 请求与响应解析等核心实现步骤。
|
2月前
|
JSON JavaScript 前端开发
Python+JAVA+PHP语言,苏宁商品详情API
调用苏宁商品详情API,可通过HTTP/HTTPS发送请求并解析响应数据,支持多种编程语言,如JavaScript、Java、PHP、C#、Ruby等。核心步骤包括构造请求URL、发送GET/POST请求及解析JSON/XML响应。不同语言示例展示了如何获取商品名称与价格等信息,实际使用时请参考苏宁开放平台最新文档以确保兼容性。
|
10月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
256 2
|
6月前
|
前端开发 Cloud Native Java
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现
博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现
|
6月前
|
缓存 安全 Java
《从头开始学java,一天一个知识点》之:字符串处理:String类的核心API
🌱 **《字符串处理:String类的核心API》一分钟速通!** 本文快速介绍Java中String类的3个高频API:`substring`、`indexOf`和`split`,并通过代码示例展示其用法。重点提示:`substring`的结束索引不包含该位置,`split`支持正则表达式。进一步探讨了String不可变性的高效设计原理及企业级编码规范,如避免使用`new String()`、拼接时使用`StringBuilder`等。最后通过互动解密游戏帮助读者巩固知识。 (上一篇:《多维数组与常见操作》 | 下一篇预告:《输入与输出:Scanner与System类》)
141 11
|
7月前
|
数据采集 存储 Java
Java爬虫获取微店店铺所有商品API接口设计与实现
本文介绍如何使用Java设计并实现一个爬虫程序,以获取微店店铺的所有商品信息。通过HttpClient发送HTTP请求,Jsoup解析HTML页面,提取商品名称、价格、图片链接等数据,并将其存储到本地文件或数据库中。文中详细描述了爬虫的设计思路、代码实现及注意事项,包括反爬虫机制、数据合法性和性能优化。此方法可帮助商家了解竞争对手,为消费者提供更全面的商品比较。
|
7月前
|
缓存 Java 应用服务中间件
java语言后台管理若依框架-登录提示404-接口异常-系统接口404异常如何处理-登录验证码不显示prod-api/captchaImage 404 (Not Found) 如何处理-解决方案优雅草卓伊凡
java语言后台管理若依框架-登录提示404-接口异常-系统接口404异常如何处理-登录验证码不显示prod-api/captchaImage 404 (Not Found) 如何处理-解决方案优雅草卓伊凡
1238 5