相关文章推荐
近视的橙子  ·  spring 集成 kafka ...·  1 月前    · 
爱听歌的凉面  ·  spring启动时只执行一次的方法实现_sp ...·  1 周前    · 
愉快的黄豆  ·  Springcloud学习笔记41--Dat ...·  昨天    · 
玉树临风的硬币  ·  SpringBoot(13)---整合Dru ...·  昨天    · 
深情的水桶  ·  qt ...·  1 年前    · 
乖乖的皮带  ·  企业号认证失败不退服务费,用户扎堆投诉,平台 ...·  1 年前    · 
悲伤的长颈鹿  ·  慕慕慕慕斯小蛋糕的个人空间-慕慕慕慕斯小蛋糕 ...·  1 年前    · 
爱健身的眼镜  ·  用Python写书法作品,WOW!-腾讯云开 ...·  1 年前    · 
曾深爱过的葡萄  ·  broadcastreceiver - ...·  1 年前    · 
Code  ›  1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知开发者社区
数据校验 hibernate bean
https://cloud.tencent.com/developer/article/1751158
慷慨大方的炒饭
1 年前
YourBatman
0 篇文章

1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知

前往专栏
腾讯云
开发者社区
文档 意见反馈 控制台
首页
学习
活动
专区
工具
TVP
最新优惠活动
文章/答案/技术大牛
发布
首页
学习
活动
专区
工具
TVP 最新优惠活动
返回腾讯云官网
YourBatman
首页
学习
活动
专区
工具
TVP 最新优惠活动
返回腾讯云官网
社区首页 > 专栏 > BAT的乌托邦 > 1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知

1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知

作者头像
YourBatman
发布 于 2020-11-24 10:49:57
955 0
发布 于 2020-11-24 10:49:57
举报

目录

  • ✍前言
  • ✍正文
    • 为什么要有数据校验?
    • 关于Jakarta EE
      • 迁移
      • Jakarta EE 9
    • Jakarta Bean Validation
      • JSR303
      • JSR349
      • JSR380
        • 新增注解
      • 使用示例
        • 校验Java Bean
        • 校验方法/校验构造器
    • 加餐:Bean Validation 3.0
      • 参考实现
  • ✍总结
          • ✔推荐阅读:
  • ♥关注A哥♥
在这里插入图片描述
在这里插入图片描述

✍前言

你好,我是YourBatman。

作为一个开发者,聊起数据校验(Bean Validation),不管是前、中、后端都耳熟能详,并且心里暗爽:so easy。

的确,对数据做校验是一个程序员的基本素质,它不难但发生在我们程序的几乎每个角落,就像下面这幅图所示: 每一层都需要做校验 。

在这里插入图片描述
在这里插入图片描述

如果你真的这么去写代码的话(每一层都写一份),肯定是不太合适的,良好的状态应该如下图所示:

在这里插入图片描述
在这里插入图片描述

作为一个Java开发者,在Spring大行其道的今天,很多小伙伴了解数据校验来自于 Spring MVC 场景,甚至 止步于此 。殊不知,Java EE早已把它抽象成了JSR标准技术,并且Spring还是借助整合它完成了自我救赎呢。

在我看来,按 Spring的3C战略 标准来比,Bean Validation数据校验这块是没有能够完成对传统Java EE的超越,自身设计存在过重、过度设计等特点。

本专栏命名为Bean Validation(数据校验),将先从JSR标准开始,再逐渐深入到具体实现Hibernate Validation、整合Spring使用场景等等。因此本专栏将让你将得到一份系统数据校验的知识。

✍正文

在任何时候 ,当你要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情。应用程序必须通过 某种手段 来确保输入进来的数据从语义上来讲是正确的,比如生日必须是过去时,年龄必须>0等等。

在这里插入图片描述
在这里插入图片描述

为什么要有数据校验?

数据校验是非常常见的工作,在日常的开发中贯穿于代码的 各个层次 ,从上层的View层到后端业务处理层,甚至底层的数据层。

我们知道通常情况下程序肯定是分层的,不同的层可能由不同的人来开发或者调用。若你是一个有经验的程序员,我相信你肯定见过在 不同的层了都出现了相同的校验代码 ,这就是某种意义上的 垃圾代码 :

public String queryValueByKey(String zhName, String enName, Integer age) {
    checkNotNull(zhName, "zhName must be not null");
    checkNotNull(enName, "enName must be not null");
    checkNotNull(age, "age must be not null");
    validAge(age, "age must be positive");
}

从这个简单的方法入参校验至少能发现如下问题:

  1. 需要写大量的代码来进行参数基本验证(这种代码多了就算垃圾代码)
  2. 需要通过文字注释来知道每个入参的约束是什么(否则别人咋看得懂)
  3. 每个程序员做参数验证的方式可能不一样,参数验证抛出的异常也不一样,导致后期几乎没法维护

如上会导致代码冗余和一些管理的问题(代码量越大,管理起来维护起来就越困难),比如说语义的一致性问题。为了避免这样的情况发生,最好是将 验证逻辑与相应的域模型进行绑定 ,这就是本文将要提供的一个新思路: Bean Validation 。

关于Jakarta EE

2018年03月, Oracle 决定把 JavaEE 移交给开源组织 Eclipse 基金会 ,并且不再使用Java EE这个名称。这是它的新logo:

在这里插入图片描述
在这里插入图片描述

对应的名称修改还包括:

旧名称

新名称

Java EE

Jakarta EE

Glassfish

Eclipse Glassfish

Java Community Process (JCP)

Eclipse EE.next Working Group (EE.next)

Oracle development management

Eclipse Enterprise for Java (EE4J) 和 Project Management Committee (PMC)

JCP 将继续支持 Java SE社区。 但是, Jakarta EE规范自此将不会在JCP下开发 。Jakarta EE标准大概由Eclipse Glassfish、Apache TomEE、Wildfly、Oracle WebLogic、JBoss、IBM、Websphere Liberty等组织来制定

迁移

既然名字都改了,那接下来就是迁移喽,毕竟Java EE这个名称(javax包名)不能再用了嘛。Eclipse接手后发布的首个 Enterprise Java 将是 Jakarta EE 9,该版本将以Java EE 8作为其基准版本(最低版本要求是Java8)。

有个意思的现象是:Java EE 8是 2019.09.10 发布的,但实际上官方名称是 Jakarta EE 8 了。很明显该版本并非由新组织设计和制定的,不是它们的产物。但是,彼时平台已更名为Jakarta有几个月了,因此对于一些Jar你在maven市场上经常能看见两种坐标:

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
<dependency>
    <groupId>jakarta.validation</groupId>
    <artifactId>jakarta.validation-api</artifactId>
    <version>2.0.1</version>
</dependency>

虽然坐标不一样,但是 内容是100%一样的 (包名均还为javax.*),很明显这是更名的过度期,为后期全面更名做准备呢。

严格来讲:只要大版本号(第一个数字)还一样,包名是 不可能 变化的,因此一般来说均具有向下兼容性

既然Jakarta释放出了更名信号,那么下一步就是彻彻底底的改变喽。果不其然,这些都在 Jakarta EE 9 里得到实施。

Jakarta EE 9

2020.08.31 ,Jakarta后的第一个企业级平台Jakarta EE 9正式发布。如果说Jakarta EE 8只是冠了个名,那么这个就名正言顺了。

小贴士:我写本文时还没到2020.08.31呢,这个时间是我在官网趴来的,因此肯定准确

这次企业平台的升级最大的亮点是:

  1. 把旗下30于种技术的 大版本号 全部+1(Jakarta RESTful Web Services除外)
  2. 包名 全部 去 javax.* 化,全部改为 jakarta.*
  3. JavaSE基准版本要求依旧保持为Java 8(而并非Java9哦)

可以发现本次升级的主要目的并着眼于功能点,仍旧是名字的替换。虽然大家对Java EE的javax有较深的情节,但旧的不去新的不来。我们以后开发过中遇到 jakarta.* 这种包名就不用再感到惊讶了,提前准备总是好的。

Jakarta Bean Validation

Jakarta Bean Validation不仅仅是一个规范,它还是一个生态。

之前名为Java Bean Validation,2018年03月之后就得改名叫Jakarta Bean Validation 喽,这不官网早已这么称呼了:

在这里插入图片描述
在这里插入图片描述

Bean Validation技术隶属于Java EE规范,期间有多个JSR( Java Specification Requests )支持,截止到稿前共有三次JSR标准发布:

在这里插入图片描述
在这里插入图片描述

说明:JCP这个组织就是来定义Java标准的,在Java行业鼎鼎有名的公司大都是JCP的成员,可以共同参与Java标准的制定,影响着世界。包括掌门人Oracle以及Eclipse、Redhat、JetBrains等等。值得天朝人自豪的是:2018年5月17日 阿里巴巴作为一员正式加入JCP组织,成为唯一一家中国公司 。

Bean Validation是标准,它的参考实现除了有我们熟悉的 Hibernate Validator 外还有 Apache BVal ,但是后者使用非常小众,忘了它吧。实际使用中,基本可以认为Hibernate Validator是Bean Validation规范的唯一参考实现,是对等的。

小贴士:Apache BVal胜在轻量级上,只有不到1m空间所以非常轻量,有些选手还是忠爱的(此项目还在发展中,并未停更哦,有兴趣你可以自己使用试试)

JSR303

这个JSR提出很早了(2009年),它为 基于注解的 JavaBean验证定义元数据模型和API,通过使用XML验证描述符覆盖和扩展元数据。JSR-303主要是对JavaBean进行验证,如方法级别(方法参数/返回值)、依赖注入等的验证是没有指定的。

作为开山之作,它规定了Java数据校验的模型和API, 这就是Java Bean Validation 1.0版本 。

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.0.0.GA</version>
</dependency>

该版本提供了常见的校验注解(共计13个):

注解

支持类型

含义

null值是否校验

@AssertFalse

bool

元素必须是false

否

@AssertTrue

bool

元素必须是true

否

@DecimalMax

Number的子类型(浮点数除外)以及String

元素必须是一个数字,且值必须<=最大值

否

@DecimalMin

同上

元素必须是一个数字,且值必须>=最大值

否

@Max

同上

同上

否

@Min

同上

同上

否

@Digits

同上

元素构成是否合法(整数部分和小数部分)

否

@Future

时间类型(包括JSR310)

元素必须为一个将来(不包含相等)的日期(比较精确到毫秒)

否

@Past

同上

元素必须为一个过去(不包含相等)的日期(比较精确到毫秒)

否

@NotNull

any

元素不能为null

是

@Null

any

元素必须为null

是

@Pattern

字符串

元素需符合指定的正则表达式

否

@Size

String/Collection/Map/Array

元素大小需在指定范围中

否

所有注解均可标注在:方法、字段、注解、构造器、入参等几乎任何地方

可以看到这些注解均为平时开发中比较常用的注解,但是在使用过程中有如下事项你仍旧需要注意:

  1. 以上所有注解对null是免疫的 ,也就是说如果你的值是null,是不会触发对应的校验逻辑的(也就说null是合法的),当然喽@NotNull / @Null除外
  2. 对于时间类型的校验注解(@Future/@Past),是开区间(不包含相等)。也就是说:如果相等就是不合法的,必须是大于或者小于
    1. 这种case比较容易出现在LocalDate这种只有日期上面,必须是将来/过去日期, 当天属于非法日期
  3. @Digits 它并不规定数字的范围,只规定了数字的结构。如:整数位最多多少位,小数位最多多少位
  4. @Size 规定了集合类型的范围(包括字符串),这个范围是 闭区间
  5. @DecimalMax和@Max 作用基本类似,大部分情况下可通用。不同点在于:@DecimalMax设置最大值是用字符串形式表示(只要合法都行,比如科学计数法),而@Max最大值设置是个long值
    1. 我个人一般用@Max即可,因为够用了~

另外可能有人会问:为毛没看见 @NotEmpty、@Email、@Positive 等常用注解?那么带着兴趣和疑问,继续往下看吧~

JSR349

该规范是2013年完成的,伴随着 Java EE 7 一起发布,它就是我们比较熟悉的Bean Validation 1.1。

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>

相较于1.0版本,它主要的改进/优化有如下几点:

  1. 标准化了Java平台的约束定义、描述、和验证
  2. 支持方法级验证(入参或返回值的验证)
  3. Bean验证组件的依赖注入
  4. 与上下文和DI依赖注入集成
  5. 使用 EL表达式 的错误消息插值,让错误消息动态化起来(强依赖于ElManager)
  6. 跨参数验证。比如密码和验证密码必须相同

小贴士:注解个数上,相较于1.0版本并没新增~

它的官方参考实现如下:

在这里插入图片描述
在这里插入图片描述

可以看到,Java Bean Validation 1.1版本实现对应的是Hibernate Validator 5.x(1.0版本对应的是4.x)

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.4.3.Final</version>
</dependency>

当你导入了 hibernate-validator 后,无需再显示导入 javax.validation 。hibernate-validator 5.x版本基本已停更,只有严重bug才会修复。因此若非特殊情况,不再建议你使用此版本,也就是不建议再使用Bean Validation 1.1版本,更别谈1.0版本喽。

小贴士:Spring Boot1.5.x默认集成的还是Bean Validation 1.1哦,但到了Boot 2.x后就彻底摒弃了老旧版本

JSR380

当下主流版本,也就是我们所说的 Java Bean Validation 2.0 和 Jakarta Bean Validation 2.0 版本。关于这两种版本的差异,官方做出了解释:

在这里插入图片描述
在这里插入图片描述

他俩除了叫法不一样、除了GAV上有变化,其它地方没任何改变。它们各自的GAV如下:

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
<dependency>
    <groupId>jakarta.validation</groupId>
    <artifactId>jakarta.validation-api</artifactId>
    <version>2.0.1</version>
</dependency>

现在应该不能再叫 Java EE 了,而应该是 Jakarta EE 。两者是一样的意思,你懂的。 Jakarta Bean Validation 2.0 是在2019年8月发布的,属于 Jakarta EE 8 的一部分。它的官方参考实现只有唯一的Hibernate validator了:

在这里插入图片描述
在这里插入图片描述

此版本具有很重要的现实意义,它主要提供如下亮点:

  1. 支持通过注解参数化类型(泛型类型)参数来验证容器内的元素,如: List<@Positive Integer> positiveNumbers
    1. 更灵活的集合类型级联验证;例如,现在可以验证映射的值和键,如: Map<@Valid CustomerType, @Valid Customer> customersByType
    2. 支持java.util.Optional类型,并且支持通过插入额外的值提取器来支持自定义容器类型
  2. 让@Past/@Future注解支持注解在JSR310时间上
  3. 新增内建的注解类型(共9个):@Email, @NotEmpty, @NotBlank, @Positive, @PositiveOrZero, @Negative, @NegativeOrZero, @PastOrPresent和@FutureOrPresent
  4. 所有内置的约束现在都支持 重复标记
  5. 使用反射检索参数名称,也就是入参名,详见这个API:ParameterNameProvider
    1. 很明显这是需要Java 8的启动参数支持的
  6. Bean验证XML描述符的名称空间已更改为:
    1. META-INF/validation.xml -> http://xmlns.jcp.org/xml/ns/validation/configuration
    2. mapping files -> http://xmlns.jcp.org/xml/ns/validation/mapping
  7. JDK最低版本要求:JDK 8

Hibernate Validator 自6.x版本开始对 JSR 380 规范提供完整支持,除了支持标准外,自己也做了相应的优化,比如性能改进、减少内存占用等等,因此用最新的版本肯定是没错的,毕竟只会越来越好嘛。

新增注解

相较于1.x版本,2.0版本在其基础上新增了9个实用注解,总数到了22个。现对新增的9个注解解释如下:

注解

支持类型

含义

null值是否校验

@Email

字符串

元素必须为电子邮箱地址

否

@NotEmpty

容器类型

集合的Size必须大于0

是

@NotBlank

字符串

字符串必须包含至少一个非空白的字符

是

@Positive

数字类型

元素必须为正数(不包括0)

否

@PositiveOrZero

同上

同上(包括0)

否

@Negative

同上

元素必须为负数(不包括0)

否

@NegativeOrZero

同上

同上(包括0)

否

@PastOrPresent

时间类型

在@Past基础上包括相等

否

@FutureOrPresent

时间类型

在@Futrue基础上包括相等

否

像 @Email、@NotEmpty、@NotBlank 之前是Hibernate额外提供的,2.0标准后hibernate自动 退位让贤 并且标注为过期了。Bean Validation 2.0的JSR规范制定负责人就职于Hibernate,所以这么做就很自然了。就是他:

在这里插入图片描述
在这里插入图片描述

小贴士:除了JSR标准提供的这22个注解外,Hibernate Validator还提供了一些非常实用的注解,这在后面讲述Hibernate Validator时再解释吧

使用示例

导入实现包:

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.5.Final</version>
</dependency>
校验Java Bean

书写JavaBean和校验程序(全部使用JSR标准API哦):

@ToString
@Setter
@Getter
public class Person {
    @NotNull
    public String name;
    @NotNull
    @Min(0)
    public Integer age;
}
public static void main(String[] args) {
    Person person = new Person();
    person.setAge(-1);
    // 1、使用【默认配置】得到一个校验工厂  这个配置可以来自于provider、SPI提供
    ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
    // 2、得到一个校验器
    Validator validator = validatorFactory.getValidator();
    // 3、校验Java Bean(解析注解) 返回校验结果
    Set<ConstraintViolation<Person>> result = validator.validate(person);
    // 输出校验结果
    result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue()).forEach(System.out::println);
}

运行程序,不幸抛错:

Caused by: java.lang.ClassNotFoundException: javax.el.ELManager
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355)
	...

上面说了,从1.1版本起就需要El管理器支持用于错误消息动态插值,因此需要自己额外导入EL的实现。

小贴士:EL也属于Java EE标准技术,可认为是一种表达式语言工具,它并不仅仅是只能用于Web(即使你绝大部分情况下都是用于web的jsp里),可以用于任意地方(类比Spring的SpEL)

这是EL技术规范的API:

<!-- 规范API -->
<dependency>
    <groupId>javax.el</groupId>
    <artifactId>javax.el-api</artifactId>
    <version>3.0.0</version>
</dependency>

Expression Language 3.0表达式语言规范发版于2013-4-29发布的,Tomcat 8、Jetty 9、GlasshFish 4都已经支持 实现 了EL 3.0,因此随意导入一个都可(如果你是web环境,根本就不用自己手动导入这玩意了)。

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-el</artifactId>
    <version>9.0.22</version>
</dependency>

添加好后,再次运行程序,控制台正常输出校验失败的消息:

age 最小不能小于0: -1
name 不能为null: null
校验方法/校验构造器

请移步下文详解。

加餐:Bean Validation 3.0

伴随着 Jakarta EE 9 的发布, Jakarta Bean Validation 3.0 也正式公诸于世。

<dependency>
    <groupId>jakarta.validation</groupId>
    <artifactId>jakarta.validation-api</artifactId>
 
推荐文章
近视的橙子  ·  spring 集成 kafka producer(KafkaTemplate) - 护花使者
1 月前
爱听歌的凉面  ·  spring启动时只执行一次的方法实现_springboottest只启动一次
1 周前
愉快的黄豆  ·  Springcloud学习笔记41--DataSource数据源,Springboot整合Druid数据库连接池;Springboot自动读取yml文件中的配置,解析为bean并注入spring容器中
昨天
玉树临风的硬币  ·  SpringBoot(13)---整合Druid实现多数据源和可视化监控 - 雨点的名字
昨天
深情的水桶  ·  qt 不点击按钮触发按钮clicked事件_百度知道
1 年前
乖乖的皮带  ·  企业号认证失败不退服务费,用户扎堆投诉,平台:有提前说明
1 年前
悲伤的长颈鹿  ·  慕慕慕慕斯小蛋糕的个人空间-慕慕慕慕斯小蛋糕个人主页-哔哩哔哩视频
1 年前
爱健身的眼镜  ·  用Python写书法作品,WOW!-腾讯云开发者社区-腾讯云
1 年前
曾深爱过的葡萄  ·  broadcastreceiver - Should I use android: process =":remote" in my receiver? - Stack Overflow
1 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号