28

5. Bean Validation声明式验证四大级别:字段、属性、容器元素、类

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzI0MTUwOTgyOQ%3D%3D&%3Bmid=2247490441&%3Bidx=1&%3Bsn=915acd1272caabac56e19a199249950d
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
640?wx_fmt=jpeg

点击上方“ 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」共支持四个级别的约束:

  1. 字段约束(Field)

  2. 属性约束(Property)

  3. 容器 「元素」 约束(Container Element)

  4. 类约束(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() 得到字段的值

使用细节

  1. 字段约束可以应用于 「任何访问修饰符」 的字段

  2. 不支持对静态字段的约束(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来获取待校验的值。

使用细节

  1. 约束放在get方法上 「优于」 放在set方法上,这样只读属性(没有get方法)依然可以执行约束逻辑

  2. 不要在 「属性和字段」 上都标注注解,否则会重复执行约束逻辑(有多少个注解就执行多少次)

  3. 不要既在属性的get方法上又在set方法上标注约束注解

3、容器元素级别约束(Container Element)

还有一种非常非常常见的验证场景:验证容器内(每个)元素,也就验证参数化类型 parameterized type 。形如 List<Room> 希望里面装的每个Room都是合法的,传统的做法是在for循环里对每个room进行验证:

List<Room> beans = new ArrayList<>();
for (Room bean : beans) {
validate(bean);
...
}

很明显这么做至少存在下面两个不足:

  1. 验证逻辑具有侵入性

  2. 验证逻辑是黑匣子(不看内部源码无法知道你有哪些约束),非声明式

在本专栏第一篇知道了从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这个注解就不是必须的了

使用细节

  1. 若约束注解想标注在容器元素上,那么注解定义的 @Target 里必须包含 TYPE_USE (Java8新增)这个类型
    1. BV和HV(除了Class级别)的 「所有注解」 均能标注在容器元素上

  2. BV规定了可以验证容器内元素,HV提供实现。它默认支持如下容器类型:

    1. java.util.Iterable 的实现(如List、Set)
    2. java.util.Map 的实现,支持key和value
    3. java.util.Optional/OptionalInt/OptionalDouble...
    4. JavaFX的 javafx.beans.observable.ObservableValue
    5. 自定义容器类型(自定义很重要,详见下篇文章)

4、类级别约束(Class)

类级别的约束验证是很多同学不太熟悉的一块,但它却很是重要。

其实Hibernate-Validator已内置提供了一部分能力,但可能还不够,很多场景需要自己动手优雅解决。为了体现此part的重要性,我决定专门撰文描述,当然还有 「自定义容器类型」 类型的校验喽,我们下文见。

字段约束和属性约束的区别

字段(Field)   VS   属性(Property)本身就属于一对“近义词”,很多时候口头上我们并不做区分,是因为 「在POJO里」 他俩一般都同时存在,因此大多数情况下可以对等沟通。比如:

@Data
public class Room {
@NotNull
private String name;
@AssertTrue
private boolean finished;
}

字段和属性的区别

  1. 字段具有存储功能:字段是类的一个成员,值在内存中真实存在;而属性它不具有存储功能,属于Java Bean规范抽象出来的一个叫法

  2. 字段一般用于类 「内部」 (一般是private),而属性可供外部访问(get/set一般是public)

    1. 这指的是一般情况下的规律

  3. 字段的本质是Field,属性的本质是Method

  4. 属性并 「不依赖于」 字段而存在,只是他们一般都成双成对出现

    1. getClass() 你可认为它有名为class的属性,但是它并没有名为class的字段

知晓了字段和属性的区别,再去理解 「字段约束」「属性约束」 的差异就简单了,它俩的差异仅仅体现在 「待验证值」 访问策略上的区别:

  • 字段约束:直接反射访问字段的值 -> Field#get(不会执行get方法体)

  • 属性约束:调用属性get方法 -> getXXX(会执行get方法体)

小贴士:如果你希望执行了验证就输出一句日志,又或者你的POJO被字节码增强了,那么属性约束更适合你。否则,推荐使用字段约束

✍总结

嗯,这篇文章还不错吧,总体浏览下来行文简单,但内容还是挺干的哈,毕竟1024节嘛,不来点的干的心里有愧。

作为此part姊妹篇的 「上篇」 ,它是每个同学都有必要掌握的使用方式。而 「下篇」 我觉得应该更为兴奋些,毕竟那里才能加分。1024,撸起袖子继续干。

✔推荐阅读:

640?wx_fmt=gif

关注A哥,开启专栏式学习

640?wx_fmt=jpeg640?wx_fmt=png

扫码关注后,回复“专栏”进入更多Spring专栏学习

右侧为私人微信(加好友备注:Java入群)

个人站点 (本文已收录) :https://www.yourbatman.cn


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK