5. Bean Validation声明式验证四大级别:字段、属性、容器元素、类
1024,代码改变世界。本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈、MyBatis、JVM、中间件等小而美的专栏供以免费学习。关注公众号【BAT的乌托邦】逐个击破,深入掌握,拒绝浅尝辄止。

✍前言
你好,我是YourBatman。又一年1024程序员节,你快乐吗?还是在加班上线呢?
上篇文章 介绍了Validator校验器的五大核心组件,在结合前面几篇所讲,相信你对Bean Validation已有了一个整体认识了。
本文将非常实用,因为将要讲述的是Bean Validation在4个层级上的验证方式,它将覆盖你使用过程中的方方面面,不信你看。
版本约定
- Bean Validation版本:
2.0.2 - Hibernate Validator版本:
6.1.5.Final
✍正文
Jakarta Bean它的验证约束是通过声明式方式(注解)来表达的,我们知道Java注解几乎可以标注在任何地方(package上都可标注注解你敢信?),那么Jakarta Bean支持哪些呢?
Jakarta Bean共支持四个级别的约束:
- 字段约束(Field)
- 属性约束(Property)
- 容器元素约束(Container Element)
- 类约束(Class)
值得注意的是,并不是所有的约束注解都能够标注在上面四种级别上。现实情况是:Bean Validation自带的22个标准约束全部支持1/2/3级别,且全部不支持第4级别(类级别)约束。当然喽,作为补充的Hibernate-Validator它提供了一些专门用于类级别的约束注解,如org.hibernate.validator.constraints.@ScriptAssert就是一常用案例。
说明:为简化接下来示例代码,共用工具代码提前展示如下:
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);
}
}
1、字段级别约束(Field)
这是我们最为常用的一种约束方式:
public class Room {
@NotNull
public String name;
@AssertTrue
public boolean finished;
}
书写测试用例:
public static void main(String[] args) {
Room bean = new Room();
bean.finished = false;
ValidatorUtil.printViolations(ValidatorUtil.obtainValidator().validate(bean));
}
运行程序,输出:
finished只能为true,但你的值是: false
name不能为null,但你的值是: null
当把约束标注在Field字段上时,Bean Validation将使用字段的访问策略来校验,不会调用任何方法,即使你提供了对应的get/set方法也不会触碰。
话外音:使用
Field#get()得到字段的值
使用细节
- 字段约束可以应用于任何访问修饰符的字段
- 不支持对静态字段的约束(static静态字段使用约束无效)
若你的对象会被字节码增强,那么请不要使用Field约束,而是使用下面介绍的属性级别约束更为合适。
原因:增强过的类并不一定能通过字段反射去获取到它的值
绝大多数情况下,对Field字段做约束的话均是POJO,被增强的可能性极小,因此此种方式是被推荐的,看着清爽。
2、属性级别约束(Property)
若一个Bean遵循Java Bean规范,那么也可以使用属性约束来代替字段约束。比如上例可改写为如下:
public class Room {
public String name;
public boolean finished;
@NotNull
public String getName() {
return name;
}
@AssertTrue
public boolean isFinished() {
return finished;
}
}
执行上面相同的测试用例,输出:
finished只能为true,但你的值是: false
name不能为null,但你的值是: null
效果“完全”一样。
当把约束标注在Property属性上时,将采用属性访问策略来获取要验证的值。说白了:会调用你的Method来获取待校验的值。
使用细节
- 约束放在get方法上优于放在set方法上,这样只读属性(没有get方法)依然可以执行约束逻辑
- 不要在属性和字段上都标注注解,否则会重复执行约束逻辑(有多少个注解就执行多少次)
- 不要既在属性的get方法上又在set方法上标注约束注解
3、容器元素级别约束(Container Element)
还有一种非常非常常见的验证场景:验证容器内(每个)元素,也就验证参数化类型parameterized type。形如List<Room>希望里面装的每个Room都是合法的,传统的做法是在for循环里对每个room进行验证:
List<Room> beans = new ArrayList<>();
for (Room bean : beans) {
validate(bean);
...
}
很明显这么做至少存在下面两个不足:
- 验证逻辑具有侵入性
- 验证逻辑是黑匣子(不看内部源码无法知道你有哪些约束),非声明式
在本专栏第一篇知道了从Bean Validation 2.0开始就支持容器元素校验了(本专栏使用版本为:2.02),下面我们来体验一把:
public class Room {
@NotNull
public String name;
@AssertTrue
public boolean finished;
}
书写测试用例:
public static void main(String[] args) {
List<@NotNull Room> rooms = new ArrayList<>();
rooms.add(null);
rooms.add(new Room());
Room room = new Room();
room.name = "YourBatman";
rooms.add(room);
ValidatorUtil.printViolations(ValidatorUtil.obtainValidator().validate(rooms));
}
运行程序,没有任何输出,也就是说并没有对rooms立面的元素进行验证。这里有一个误区:Bean Validator是基于Java Bean进行验证的,而此处你的rooms仅仅只是一个容器类型的变量而已,因此不会验证。
其实它是把List当作一个Bean,去验证List里面的标注有约束注解的属性/方法。很显然,List里面不可能标注有约束注解嘛,所以什么都不输出喽
为了让验证生效,我们只需这么做:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Rooms {
private List<@Valid @NotNull Room> rooms;
}
public static void main(String[] args) {
List<@NotNull Room> beans = new ArrayList<>();
beans.add(null);
beans.add(new Room());
Room room = new Room();
room.name = "YourBatman";
beans.add(room);
// 必须基于Java Bean,验证才会生效
Rooms rooms = new Rooms(beans);
ValidatorUtil.printViolations(ValidatorUtil.obtainValidator().validate(rooms));
}
运行程序,输出:
rooms[0].<list element>不能为null,但你的值是: null
rooms[2].finished只能为true,但你的值是: false
rooms[1].name不能为null,但你的值是: null
rooms[1].finished只能为true,但你的值是: false
rooms[1].finished只能为true,但你的值是: false
从日志中可以看出,元素的验证顺序是不保证的。
小贴士:在HV 6.0 之前的版本中,验证容器元素时@Valid是必须,也就是必须写成这样:
List<@Valid @NotNull Room> rooms才有效。在HV 6.0之后@Valid这个注解就不是必须的了
使用细节
- 若约束注解想标注在容器元素上,那么注解定义的
@Target里必须包含TYPE_USE(Java8新增)这个类型- BV和HV(除了Class级别)的所有注解均能标注在容器元素上
- BV规定了可以验证容器内元素,HV提供实现。它默认支持如下容器类型:
java.util.Iterable的实现(如List、Set)java.util.Map的实现,支持key和valuejava.util.Optional/OptionalInt/OptionalDouble...- JavaFX的
javafx.beans.observable.ObservableValue - 自定义容器类型(自定义很重要,详见下篇文章)
4、类级别约束(Class)
类级别的约束验证是很多同学不太熟悉的一块,但它却很是重要。
其实Hibernate-Validator已内置提供了一部分能力,但可能还不够,很多场景需要自己动手优雅解决。为了体现此part的重要性,我决定专门撰文描述,当然还有自定义容器类型类型的校验喽,我们下文见。
字段约束和属性约束的区别
字段(Field) VS 属性(Property)本身就属于一对“近义词”,很多时候口头上我们并不做区分,是因为在POJO里他俩一般都同时存在,因此大多数情况下可以对等沟通。比如:
@Data
public class Room {
@NotNull
private String name;
@AssertTrue
private boolean finished;
}
字段和属性的区别
- 字段具有存储功能:字段是类的一个成员,值在内存中真实存在;而属性它不具有存储功能,属于Java Bean规范抽象出来的一个叫法
- 字段一般用于类内部(一般是private),而属性可供外部访问(get/set一般是public)
- 这指的是一般情况下的规律
- 字段的本质是Field,属性的本质是Method
- 属性并不依赖于字段而存在,只是他们一般都成双成对出现
- 如
getClass()你可认为它有名为class的属性,但是它并没有名为class的字段
- 如
知晓了字段和属性的区别,再去理解字段约束和属性约束的差异就简单了,它俩的差异仅仅体现在待验证值访问策略上的区别:
- 字段约束:直接反射访问字段的值 -> Field#get(不会执行get方法体)
- 属性约束:调用属性get方法 -> getXXX(会执行get方法体)
小贴士:如果你希望执行了验证就输出一句日志,又或者你的POJO被字节码增强了,那么属性约束更适合你。否则,推荐使用字段约束
✍总结
嗯,这篇文章还不错吧,总体浏览下来行文简单,但内容还是挺干的哈,毕竟1024节嘛,不来点的干的心里有愧。
作为此part姊妹篇的上篇,它是每个同学都有必要掌握的使用方式。而下篇我觉得应该更为兴奋些,毕竟那里才能加分。1024,撸起袖子继续干。
推荐阅读:
- 1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知
- 2. Bean Validation声明式校验方法的参数、返回值
- 3. 站在使用层面,Bean Validation这些标准接口你需要烂熟于胸
- 4. Validator校验器的五大核心组件,一个都不能少
5. Bean Validation声明式验证四大级别:字段、属性、容器元素、类的更多相关文章
- 2. Bean Validation声明式校验方法的参数、返回值
你必须非常努力,才能干起来毫不费力.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免费学习.关注公众 ...
- 9、 Struts2验证(声明式验证、自定义验证器)
1. 什么是Struts2 验证器 一个健壮的 web 应用程序必须确保用户输入是合法.有效的. Struts2 的输入验证 基于 XWork Validation Framework 的声明式验证: ...
- C#中手工进行声明式验证,从此远离if验证
今天在一个ASP.NET MVC Controller Action中写代码时,需要对ViewModel的字段进行验证.但这个Action处理的是手工编写的ajax请求(不是表单提交),无法使用ASP ...
- [原创]java WEB学习笔记70:Struts2 学习之路-- 输入验证,声明式验证,声明是验证原理
本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...
- java struts2入门学习--基于xml文件的声明式验证
一.知识点总结 后台验证有两种实现方式: 1 手工验证顺序:validateXxx(针对Action中某个业务方法验证)--> validate(针对Action中所有的业务方法验证) 2 声明 ...
- Spring -12 -声明式事务及完整的XML配置文件信息 -声明式事务中的相关属性(tx:advice的标签)
1.编程式事务: 1.1由程序员编程事务控制代码. 1.2OpenSessionInView 就属于编程式事务: session.commit()和rollback() 2.声明式事务: 2.1事务控 ...
- Spring中声明式事务的几个属性的解释
声明式事务 @Transactional (通常用在service层)事务属性:传播行为,隔离级别,回滚,只读,过期 1,spring支持事务传播行为:propagation(常用以下两个) ① ...
- 第二天(1)声明式验证之使用验证框架验证域模型和ModelDriven验证
有一类特殊的属性,即这个属性的类型是另外一个JavaBean,如有一个User类,代码如下: package data; public class User { private String name ...
- 【转载】C#后台声明式验证,远离if验证
ViewModel public class ViewModel { [Required(ErrorMessage="标题不能为空")] public string Title { ...
随机推荐
- redis集群简介
1.1 集群的概念 所谓的集群,就是通过添加服务器的数量,提供相同的服务,从而让服务器达到一个稳定.高效的状态. 1.1.1 使用redis集群的必要性 问题:我们已经部署好 ...
- ASP.NET Core 基于声明的访问控制到底是什么鬼?
从ASP.NET 4.x到ASP.NET Core,内置身份验证已从基于角色的访问控制(RBAC)转变为基于声明的访问控制(CBAC). 我们常用的HttpContext.User属性ASP.NET ...
- 解决 webpack .\src\main.js .\dist\bundle.js 错误
打包的命令格式:webpack 要打包的文件的路径 打包好的输出文件的路径 栗子: webpack .\src\main.js .\dist\bundle.js 提示错误,错误信息如下: 错误原因 w ...
- Springer editorial manager上传latex文件
Springer的投稿系统editorial manager在初次投稿时只需要上传pdf文件,修改后要求上传Latex源文件.上传过程遇到好多问题,花了快两天才搞定,整理如下: 1. 主要上传的文件包 ...
- spark-4-文件读写
hdfs文件读写报错: AccessControlException: Permission denied: user=root, access=WRITE, inode="/user/ch ...
- Windows10下JDK8的下载安装与环境变量的配置
Windows10下JDK8的下载安装与环境变量的配置 下载JDK8(64位) 链接:https://pan.baidu.com/s/10ZMK7NB68kPORZsPOhivog 提取码:agsa ...
- 074 01 Android 零基础入门 01 Java基础语法 09 综合案例-数组移位 06 综合案例-数组移位-主方法功能3的实现
074 01 Android 零基础入门 01 Java基础语法 09 综合案例-数组移位 06 综合案例-数组移位-主方法功能3的实现 本文知识点:综合案例-数组移位-主方法功能3的实现 说明:因为 ...
- vue中解决chrome浏览器自动播放音频 和MP3语音打包到线上
一.vue中解决chrome浏览器自动播放音频 需求 有新订单的时候,页面自动语音提示和弹出提示框: 问题 chrome浏览器在18年4月起,就在桌面浏览器全面禁止了音视频的自动播放功能.严格地来说, ...
- 九、Python+Selenium模拟登录
研究QQ登录规则的话,得分析大量Javascript的加密解密,比较耗时间.自己也是练习很少,短时间成功不了.所以走了个捷径. Selenium是一个WEB自动化测试工具,它运行时会直接实例化出一个浏 ...
- Pock 把 Touch Bar 变成系统中的 Dock 栏
Pock 把 Touch Bar 变成系统中的 Dock 栏 Pock 是一款 macOS App,你可以通过它把 Touch Bar 变成系统中的 Dock 栏,直接用来切换和启动 App,尽享全屏 ...