乔丹是我听过的篮球之神,科比是我亲眼见过的篮球之神。本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈、MyBatis、JVM、中间件等小而美的专栏供以免费学习。关注公众号【BAT的乌托邦】逐个击破,深入掌握,拒绝浅尝辄止。

目录

  • ✍前言
    • 版本约定
  • ✍正文
    • Validator
      • validate:校验Java Bean
      • validateProperty:校验指定属性
      • validateValue:校验value值
      • 获取Class类型描述信息
      • 获得Executable校验器
    • ConstraintViolation
    • ValidatorContext
      • 方式一:自己new
      • 方式二:工厂生成
    • 获得Validator实例的两种姿势
      • 方式一:工厂直接获取
      • 方式二:从上下文获取
  • ✍总结
    • ✔推荐阅读:
  • ♥关注A哥♥

✍前言

你好,我是YourBatman。

通过前两篇文章的叙述,相信能勾起你对Bean Validation的兴趣。那么本文就站在一个使用者的角度来看,要使用Bean Validation完成校验的话我们应该掌握、熟悉哪些接口、接口方法呢?

版本约定

  • Bean Validation版本:2.0.2
  • Hibernate Validator版本:6.1.5.Final

✍正文

Bean Validation属于Java EE标准技术,拥有对应的JSR抽象,因此我们实际使用过程中仅需要面向标准使用即可,并不需要关心具体实现(是hibernate实现,还是apache的实现并不重要),也就是我们常说的面向接口编程

Tips:为了方便下面做示例讲解,对一些简单、公用的方法抽取如下:

public abstract class ValidatorUtil {public static ValidatorFactory obtainValidatorFactory() {return Validation.buildDefaultValidatorFactory();}public static Validator obtainValidator() {return obtainValidatorFactory().getValidator();}public static ExecutableValidator obtainExecutableValidator() {return obtainValidator().forExecutables();}public static <T> void printViolations(Set<ConstraintViolation<T>> violations) {violations.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue()).forEach(System.out::println);}}

Validator

校验器接口:校验的入口,可实现对Java Bean、某个属性、方法、构造器等完成校验。

public interface Validator {...
}

它是使用者接触得最多的一个API,当然也是最重要的喽。因此下面对其每个方法做出解释+使用示例。

validate:校验Java Bean

<T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);

验证Java Bean对象上的所有约束。示例如下:

Java Bean:

@ScriptAssert(script = "_this.name==_this.fullName", lang = "javascript")
@Data
public class User {@NotNullprivate String name;@Length(min = 20)@NotNullprivate String fullName;
}@Test
public void test5() {User user = new User();user.setName("YourBatman");Set<ConstraintViolation<User>> result = ValidatorUtil.obtainValidator().validate(user);ValidatorUtil.printViolations(result);
}

说明:@ScriptAssert是Hibernate Validator提供的一个脚本约束注解,可以实现垮字段逻辑校验,功能非常之强大,后面详解

运行程序,控制台输出:

执行脚本表达式"_this.name==_this.fullName"没有返回期望结果: User(name=YourBatman, fullName=null)
fullName 不能为null: null

符合预期。值得注意的是:针对fullName中的@Length约束来说,null是合法的哟,所以不会有相应日志输出的

校验Java Bean所有约束中的所有包括:
1、属性上的约束
2、类上的约束

validateProperty:校验指定属性

<T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?>... groups);

校验某个Java Bean中的某个属性上的所有约束。示例如下:

@Test
public void test6() {User user = new User();user.setFullName("YourBatman");Set<ConstraintViolation<User>> result = ValidatorUtil.obtainValidator().validateProperty(user, "fullName");ValidatorUtil.printViolations(result);
}

运行程序,控制台输出:

fullName 长度需要在20和2147483647之间: YourBatman

符合预期。它会校验属性上的所有约束,注意只是属性上的哦,其它地方的不管。

validateValue:校验value值

校验某个value值,是否符合指定属性上的所有约束。可理解为:若我把这个value值赋值给这个属性,是否合法?

<T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType,String propertyName,Object value,Class<?>... groups);

这个校验方法比较特殊:不用先存在对象实例,直接校验某个值是否满足某个属性的所有约束,所以它可以做事钱校验判断,还是挺好用的。示例如下:

@Test
public void test7() {Set<ConstraintViolation<User>> result = ValidatorUtil.obtainValidator().validateValue(User.class, "fullName", "A哥");ValidatorUtil.printViolations(result);
}

运行程序,输出:

fullName 长度需要在20和2147483647之间: A哥

若程序改为:.validateValue(User.class, "fullName", "YourBatman-YourBatman");,再次运行程序,控制台将不再输出(字符串长度超过20,合法了嘛)。

获取Class类型描述信息

BeanDescriptor getConstraintsForClass(Class<?> clazz);

这个clazz可以是类or接口类型。BeanDescriptor:描述受约束的Java Bean和与其关联的约束。示例如下:

@Test
public void test8() {BeanDescriptor beanDescriptor = obtainValidator().getConstraintsForClass(User.class);System.out.println("此类是否需要校验:" + beanDescriptor.isBeanConstrained());// 获取属性、方法、构造器的约束Set<PropertyDescriptor> constrainedProperties = beanDescriptor.getConstrainedProperties();Set<MethodDescriptor> constrainedMethods = beanDescriptor.getConstrainedMethods(MethodType.GETTER);Set<ConstructorDescriptor> constrainedConstructors = beanDescriptor.getConstrainedConstructors();System.out.println("需要校验的属性:" + constrainedProperties);System.out.println("需要校验的方法:" + constrainedMethods);System.out.println("需要校验的构造器:" + constrainedConstructors);PropertyDescriptor fullNameDesc = beanDescriptor.getConstraintsForProperty("fullName");System.out.println(fullNameDesc);System.out.println("fullName属性的约束注解个数:"fullNameDesc.getConstraintDescriptors().size());
}

运行程序,输出:

此类是否需要校验:true
需要校验的属性:[PropertyDescriptorImpl{propertyName=name, cascaded=false}, PropertyDescriptorImpl{propertyName=fullName, cascaded=false}]
需要校验的方法:[]
需要校验的构造器:[]
PropertyDescriptorImpl{propertyName=fullName, cascaded=false}
fullName属性的约束注解个数:2

获得Executable校验器

@since 1.1
ExecutableValidator forExecutables();

Validator这个API是1.0就提出的,它只能校验Java Bean,对于方法、构造器的参数、返回值等校验还无能为力。

这不1.1版本就提供了ExecutableValidator这个API解决这类需求,它的实例可通过调用Validator的该方法获得,非常方便。关于ExecutableValidator的具体使用请移步上篇文章。

ConstraintViolation

约束违反详情。此对象保存了违反约束的上下文以及描述消息。

// <T>:root bean
public interface ConstraintViolation<T> {}

简单的说,它保存着执行完所有约束后(不管是Java Bean约束、方法约束等等)的结果,提供了访问结果的API,比较简单:

小贴士:只有违反的约束才会生成此对象哦。违反一个约束对应一个实例

// 已经插值(interpolated)的消息
String getMessage();
// 未插值的消息模版(里面变量还未替换,若存在的话)
String getMessageTemplate();// 从rootBean开始的属性路径。如:parent.fullName
Path getPropertyPath();
// 告诉是哪个约束没有通过(的详情)
ConstraintDescriptor<?> getConstraintDescriptor();

示例:略。

ValidatorContext

校验器上下文,根据此上下文创建Validator实例。不同的上下文可以创建出不同实例(这里的不同指的是内部组件不同),满足各种个性化的定制需求。

ValidatorContext接口提供设置方法可以定制校验器的核心组件,它们就是Validator校验器的五大核心组件:

public interface ValidatorContext {ValidatorContext messageInterpolator(MessageInterpolator messageInterpolator);ValidatorContext traversableResolver(TraversableResolver traversableResolver);ValidatorContext constraintValidatorFactory(ConstraintValidatorFactory factory);ValidatorContext parameterNameProvider(ParameterNameProvider parameterNameProvider);ValidatorContext clockProvider(ClockProvider clockProvider);// @since 2.0 值提取器。// 注意:它是add方法,属于添加哦ValidatorContext addValueExtractor(ValueExtractor<?> extractor);Validator getValidator();
}

可以通过这些方法设置不同的组件实现,设置好后再来个getValidator()就得到一个定制化的校验器,不再千篇一律喽。所以呢,首先就是要得到ValidatorContext实例,下面介绍两种方法。

方式一:自己new

@Test
public void test2() {ValidatorFactoryImpl validatorFactory = (ValidatorFactoryImpl) ValidatorUtil.obtainValidatorFactory();// 使用默认的Context上下文,并且初始化一个Validator实例// 必须传入一个校验器工厂实例哦ValidatorContext validatorContext = new ValidatorContextImpl(validatorFactory).parameterNameProvider(new DefaultParameterNameProvider()).clockProvider(DefaultClockProvider.INSTANCE);// 通过该上下文,生成校验器实例(注意:调用多次,生成实例是多个哟)System.out.println(validatorContext.getValidator());
}

运行程序,控制台输出:

org.hibernate.validator.internal.engine.ValidatorImpl@1757cd72

这种是最直接的方式,想要啥就new啥嘛。不过这么使用是有缺陷的,主要体现在这两个方面:

  1. 不够抽象。new的方式嘛,和抽象谈不上关系
  2. 强耦合了Hibernate Validator的API,如:org.hibernate.validator.internal.engine.ValidatorContextImpl#ValidatorContextImpl

方式二:工厂生成

上面即使通过自己new的方式得到ValidatorContext实例也需要传入校验器工厂,那还不如直接使用工厂生成呢。恰好ValidatorFactory也提供了对应的方法:

ValidatorContext usingContext();

该方法用于得到一个ValidatorContext实例,它具有高度抽象、与底层API无关的特点,是推荐的获取方式,并且使用起来有流式编程的效果,如下所示:

@Test
public void test3() {Validator validator = ValidatorUtil.obtainValidatorFactory().usingContext().parameterNameProvider(new DefaultParameterNameProvider()).clockProvider(DefaultClockProvider.INSTANCE).getValidator();
}

很明显,这种方式是被推荐的。

获得Validator实例的两种姿势

在文章最后,再回头看看Validator实例获取的两种姿势。Validator校验器接口是完成数据校验(Java Bean校验、方法校验等)最主要API,经过了上面的讲述,下面可以来个获取方式的小总结了。

方式一:工厂直接获取

@Test
public void test3() {Validator validator = ValidatorUtil.obtainValidatorFactory().getValidator();
}

这种方式十分简单、简约,对初学者十分的友好,入门简单,优点明显。各组件全部使用默认方式,省心。如果要挑缺点那肯定也是有的:无法满足个性化、定制化需求,说白了:无法自定义五大组件 + 值提取器的实现。

作为这么优秀的Java EE标准技术,怎么少得了对扩展的开放呢?继续方式二吧~

方式二:从上下文获取

校验器上下文也就是ValidatorContext喽,它的步骤是先得到上下文实例,然后做定制,再通过上下文实例创建出Validator校验器实例了。

示例代码:

@Test
public void test3() {Validator validator = ValidatorUtil.obtainValidatorFactory().usingContext().parameterNameProvider(new DefaultParameterNameProvider()).clockProvider(DefaultClockProvider.INSTANCE).getValidator();
}

这种方式给与了极大的定制性,你可以任意指定核心组件实现,来达到自己的要求。

这两种方式结合起来,不就是典型的默认 + 定制扩展的搭配麽?另外,Validator是线程安全的,一般来说一个应用只需要初始化一个 Validator实例即可,所以推荐使用方式二进行初始化,对个性扩展更友好。

✍总结

本文站在一个使用者的角度去看如何使用Bean Validation,以及哪些标准的接口API是必须掌握了,有了这些知识点在平时绝大部分case都能应对自如了。

规范接口/标准接口一般能解决绝大多数问题,这就是规范的边界,有些可为,有些不为

当然喽,这些是基本功。要想深入理解Bean Validation的功能,必须深入了解Hibernate Validator实现,因为有些比较常用的case它做了很好的补充,咱们下文见。

✔推荐阅读:
  • 1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知
  • 2. Bean Validation声明式校验方法的参数、返回值

♥关注A哥♥

Author A哥(YourBatman)
个人站点 www.yourbatman.cn
E-mail yourbatman@qq.com
微 信 fsx641385712
活跃平台
公众号 BAT的乌托邦(ID:BAT-utopia)
知识星球 BAT的乌托邦
每日文章推荐 每日文章推荐

3. 站在使用层面,Bean Validation这些标准接口你需要烂熟于胸相关推荐

  1. 基于Java Bean Validation对Request参数进行校验的设计思路

    数据校验是任何一个应用程序都会用到的功能,无论是显示层还是持久层. 通常,相同的校验逻辑会分散在各个层中, 这样,不仅浪费了时间还会导致重复代码的发生. 为了避免重复, 开发人员经常会把这些校验逻辑直 ...

  2. java validation_java bean validation 参数验证

    一.前言 在后台开发过程中,对参数的校验成为开发环境不可缺少的一个环节.比如参数不能为null,email那么必须符合email的格式,如果手动进行if判断或者写正则表达式判断无意开发效率太慢,在时间 ...

  3. JSR-303 Bean Validation 介绍及 Spring MVC 服务端验证最佳实践

    任何时候,当要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情. 应用程序必须通过某种手段来确保输入参数在上下文来说是正确的. 分层的应用在很多时候,同样的数据验证逻辑会出现在不同的层, ...

  4. JSR380(Bean Validation 2.0)

    1.新的变化 支持验证容器元素 集合类型的级联验证. 支持java.util.Optional 支持JavaFX的属性类型 支持自定义容器类型 支持新的日期/时间类型 新的内置约束:@Email, @ ...

  5. JSR349(Bean Validation 1.1)

    1.新增变化 1.1 依赖注入 Bean Validation使用组件MessageInterpolator, TraversableResolver, ParameterNameProvider, ...

  6. JSR303(Bean Validation 1.0)

    Bean Validation的1.0版本 1.约束定义 1.1 约束注解 Constraint 可用于字段.方法.属性.类型.注解类型,validatedBy返回的是ConstraintValida ...

  7. Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC

    http://sishuok.com/forum/blogPost/list/7798.html 在之前的<跟我学SpringMVC>中的<第七章 注解式控制器的数据验证.类型转换及 ...

  8. JSR 303 – Bean Validation 介绍及最佳实践

    关于 Bean Validation 在任何时候,当你要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情.应用程序必须通过某种手段来确保输入进来的数据从语义上来讲是正确的.在通常的情况下 ...

  9. JSR 303 - Bean Validation 介绍及最佳实践

    关于 Bean Validation 在任何时候,当你要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情.应用程序必须通过某种手段来确保输入进来的数据从语义上来讲是正确的.在通常的情况下 ...

最新文章

  1. 分享5个有趣的 JavaScript 代码片段
  2. html background缩放,background-size使用详解
  3. java中输入char类型_java中如何输入char类型
  4. 集合-2(Set(HashSet、TreeSet、LinkedHashSet)、List(ArrayList、LinkedList、Vector)、Map(HashMap、TreeMap...))
  5. Boost:基于Boost的发送者和接收者的测试程序
  6. 【Nginx】Nginx配置文件参数/启动参数详解;启动/停止/重新加载配置命令
  7. JUC队列-LinkedBlockingQueue(二)
  8. MIPI屏数据发送命令解析
  9. java 父子级json组装不用递归_初级Java程序员如何进阶
  10. Dato for Mac(菜单栏时钟工具)支持m1
  11. Random walk 和 random walk with Restart理解
  12. 格签名困难假设: 最短向量问题SVP
  13. 华为路由器配置备忘录
  14. 雪球网股票用户评论爬虫
  15. java中成员变量和局部变量的区别
  16. 简述窄带与宽带信号的区别
  17. Ringbuffer同步问题分析
  18. 诚之和:太平鸟难渡“抄袭劫”?
  19. 发酵罐设计软件测试,发酵罐设计的心得体会
  20. 自动驾驶分级 - 练习测验

热门文章

  1. 重装系统后计算机无法启动,电脑小白一键重装系统后无法开机
  2. 修复Pubwin EP置疑数据库
  3. k8s高可用环境部署7(Dashboard and metrics)
  4. 数据分析应关注AARRR模型的哪些指标
  5. html添加markdown,为自己的网站添加Markdown——showdown.js使用教程
  6. 超详细!Win10(UEFI启动)安装Ubuntu18.04双系统
  7. tomcat去掉项目名称直接访问项目
  8. Android中的UI组件
  9. 前端效果 -- 实现折叠、展开动画效果
  10. 统计年龄分布情况(5岁的间隔统计),绘制出年龄分布图。