Bean Validation起源篇----01
Posted 大忽悠爱忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Bean Validation起源篇----01相关的知识,希望对你有一定的参考价值。
Bean Validation起源篇----01
为什么需要数据校验
数据校验是Web开发中必不可少的一环,当一个Http请求发出开始,从前端到后端的控制层,再到业务层,再到数据访问层,最终到达数据库,这其中的每一环,都需要数据校验,可见其重要性。
因为,每一层都需要有进行数据校验的要求,但是,我们不可能真的在每一层都进行一遍数据校验,而是会在控制层或者业务层完成需要的数据校验即可。
一般会将请求参数封装为一个Model对象,然后对该对象进行数据校验接口,如果校验没有问题,就可以沿着工作流,不断传递下去,后续也就不需要进行数据校验了。
但是由于大型程序通常都是会分层的,不同的层如果由不同的程序员来开发的话,就不免会写多套数据校验逻辑,这样就会导致代码冗余,出现很多如下所示的重复代码:
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");
...
这其实就是传统的数据校验逻辑,它的缺点如下:
- 大量校验代码掺杂在了业务代码之中,导致代码可读性降低,维护难度变高,代码臃肿不堪
- 无法通过一眼看出方法的入参限制要求是什么,需要加以大量注释进行说明
- 每个程序员做参数验证的方式可能不一样,参数验证抛出的异常也不一样,导致后期几乎没法维护
怎么解决呢?
- 尝试将数据校验逻辑从业务层剥离出来,将数据校验逻辑和域对象Model绑定
这里先给出一个简单的例子:
public Boolean updateStu(Stu stu)
stuDao.update(stu);
return true;
可以看到,上面并没有对stu对象的校验,那么将数据校验逻辑和域对象Model绑定,这是怎么完成的呢?
有很多种方法,这里给出一个最为easy的实现,如下:
@Data
public class Stu
Integer num;
String name;
public Integer getNum()
return num;
public void setNum(Integer num)
if(num<0)
throw new IllegalArgumentException("num不能小于0");
this.num = num;
public String getName()
return name;
public void setName(String name)
if(name!=null && !name.isEmpty())
throw new IllegalArgumentException("name不能为空");
this.name = name;
只需求确保请求参数绑定到Model对象的过程是通过调用Model对象的setter方法完成的即可。
但是,上面这种写法也存在诸多的问题,例如: 数据校验的异常异常没有统一化,返回的错误结果格式也不统一,那么还能怎么优化呢?
下面就来看看Java 官方为我们提供的Bean Validation数据校验体系吧 !
Bean Validation的前世今生
Jakarta Bean Validation
Jakarta Bean Validation不仅仅是一个规范,它还是一个生态。
之前名为Java Bean Validation,2018年03月之后就得改名叫Jakarta Bean Validation,Bean Validation技术隶属于Java EE规范.
Bean Validation是标准,它的参考实现除了有我们熟悉的Hibernate Validator外还有Apache BVal,但是后者使用非常小众,忘了它吧。实际使用中,基本可以认为Hibernate Validator是Bean Validation规范的唯一参考实现,是对等的。
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是免疫的,也就是说如果你的值是null,是不会触发对应的校验逻辑的(也就说null是合法的),当然@NotNull / @Null除外.
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版本,它主要的改进/优化有如下几点:
- 标准化了Java平台的约束定义、描述、和验证
- 支持方法级验证(入参或返回值的验证)
- Bean验证组件的依赖注入
- 与上下文和DI依赖注入集成
- 使用EL表达式的错误消息插值,让错误消息动态化起来(强依赖于ElManager)
- 跨参数验证。比如密码和验证密码必须相同
它的官方参考实现如下:
可以看到,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了:
此版本具有很重要的现实意义,它主要提供如下亮点:
- 支持通过注解参数化类型(泛型类型)参数来验证容器内的元素,如:List<@Positive Integer> positiveNumbers
- 更灵活的集合类型级联验证;例如,现在可以验证映射的值和键,如:Map<@Valid CustomerType, @Valid Customer> customersByType
- 支持java.util.Optional类型,并且支持通过插入额外的值提取器来支持自定义容器类型
- 让@Past/@Future注解支持注解在JSR310时间上
- 新增内建的注解类型(共9个):@Email, @NotEmpty, @NotBlank, @Positive, @PositiveOrZero, @Negative, @NegativeOrZero, @PastOrPresent和@FutureOrPresent
- 所有内置的约束现在都支持重复标记
- 使用反射检索参数名称,也就是入参名,详见这个API:ParameterNameProvider—很明显这是需要Java 8的启动参数支持的
- 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 - JDK最低版本要求:JDK 8
Hibernate Validator自6.x版本开始对JSR 380规范提供完整支持,除了支持标准外,自己也做了相应的优化,比如性能改进、减少内存占用等等,因此用最新的版本肯定是没错的,毕竟只会越来越好嘛。
新增注解:
相较于1.x版本,2.0版本在其基础上新增了9个实用注解,总数到了22个。现对新增的9个注解解释如下:
使用示例
导入实现包:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.5.Final</version>
</dependency>
校验Java Bean:
书写JavaBean和校验程序(全部使用JSR标准API哦):
@Data
public class Stu
@Min(value = 0)
Integer num;
@NotNull
String name;
@Test
public void testBeanValidator()
Stu stu = new Stu();
stu.setNum(-1);
// 1、使用【默认配置】得到一个校验工厂 这个配置可以来自于provider、SPI提供
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
// 2、得到一个校验器
Validator validator = validatorFactory.getValidator();
// 3、校验Java Bean(解析注解) 返回校验结果
Set<ConstraintViolation<Stu>> result = validator.validate(stu);
// 输出校验结果
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>
添加好后,再次运行程序,控制台正常输出校验失败的消息:
num 最小不能小于0: -1
name 不能为null: null
参考
1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知
以上是关于Bean Validation起源篇----01的主要内容,如果未能解决你的问题,请参考以下文章