3. 站在使用层面,Bean Validation这些标准接口你需要烂熟于胸
乔丹是我听过的篮球之神,科比是我亲眼见过的篮球之神。本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈、MyBatis、JVM、中间件等小而美的专栏供以免费学习。关注公众号【BAT的乌托邦】逐个击破,深入掌握,拒绝浅尝辄止。
✍前言
你好,我是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 {
@NotNull
private String name;
@Length(min = 20)
@NotNull
private 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啥嘛。不过这么使用是有缺陷的,主要体现在这两个方面:
- 不够抽象。new的方式嘛,和抽象谈不上关系
- 强耦合了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它做了很好的补充,咱们下文见。
推荐阅读:
3. 站在使用层面,Bean Validation这些标准接口你需要烂熟于胸的更多相关文章
- 5. Bean Validation声明式验证四大级别:字段、属性、容器元素、类
1024,代码改变世界.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免费学习.关注公众号[BAT的 ...
- Java数据校验(Bean Validation / JSR303)
文档: http://beanvalidation.org/1.1/spec/ API : http://docs.jboss.org/hibernate/beanvalidation/spec/1. ...
- Java Bean Validation 最佳实践
参数校验是我们程序开发中必不可少的过程.用户在前端页面上填写表单时,前端js程序会校验参数的合法性,当数据到了后端,为了防止恶意操作,保持程序的健壮性,后端同样需要对数据进行校验.后端参数校验最简单的 ...
- spring 3.1 配置 JCR 303 Bean Validation
A) 导入Hibernate-Validator 要使用JSR303 校验框架, 需要加入框架的具体实现Hibernate-Validator, 在soureforge上下载最新的Hibernate ...
- Java bean validation 规范与参考实现
1.Apache Bval 依赖包:validation-api-1.1.0.Final.jar org.apache.bval.bundle-1.1.1.jar bval-core-1.1.1.ja ...
- JSR 303 - Bean Validation 介绍及最佳实践
JSR 303 - Bean Validation 介绍及最佳实践 JSR 303 – Bean Validation 是一个数据验证的规范,2009 年 11 月确定最终方案.2009 年 12 月 ...
- 使用hibernate时出现 org.hibernate.HibernateException: Unable to get the default Bean Validation factory
hibernate 在使用junit测试报错: org.hibernate.HibernateException: Unable to get the default Bean Validation ...
- 基于springmvc的简单增删改查实现---中间使用到了bean validation
package com.kite.controller; import java.util.HashMap; import java.util.Map; import javax.validation ...
- Bean Validation 技术规范特性概述
概述 Bean Validation 规范 Bean 是 Java Bean 的缩写.在 Java 分层架构的实际应用中,从表示层到持久化层.每一层都须要对 Java Bean 进行业务符合性验证,如 ...
随机推荐
- 学会这些Python美图技巧,就等着女朋友夸你吧
一.前言 Python中有许多用于图像处理的库,像是Pillow,或者是OpenCV.而很多时候感觉学完了这些图像处理模块没有什么用,其实只是你不知道怎么用罢了.今天就给大家带了一些美图技巧,让你的图 ...
- 5 年 Python 的我,总结了这 90 条写 Python 程序的建议
自己写 Python 也有四五年了,一直是用自己的“强迫症”在维持自己代码的质量.都有去看Google的Python代码规范,对这几年的工作经验,做个简单的笔记,如果你也在学pythpn,准备要学习p ...
- Linux学习笔记之ubuntu安装与配置
1.打开虚拟机,点击新建虚拟机 2.安装向导 选择自定义安装 点击包含一个空白的硬盘 选择linux操作系统,版本是ubuntu 设置虚拟机的名称,可以自己写,还有保存的位置也可自选 根据自己电脑性能 ...
- Nginx WebUI管理
简介 NginxWebUI是一款方便实用的nginx 网页配置工具,可以使用 WebUI 配置 Nginx 的各项功能,包括端口转发,反向代理,ssl 证书配置,负载均衡等,最终生成「nginx.co ...
- 记一次生产环境tomcat线程数打满情况分析
前言 旨在分享工作中遇到的各种问题及解决思路与方案,与大家一起学习. -- 学无止境, 加油 ! Just do it ! 问题描述 运行环境描述 tomcat-8.5 单节点(该应用集群20个节点) ...
- 总结vue知识体系之实用技巧
vue 作为目前前端三大框架之一,对于前端开发者可以说是必备技能.那么怎么系统地学习和掌握 vue 呢?为此,我做了简单的知识体系体系总结,不足之处请各位大佬多多包涵和指正,如果喜欢的可以点个小赞!本 ...
- 金题大战Vol.0 B、序列
金题大战Vol.0 B.序列 题目描述 给定两个长度为 \(n\) 的序列\(a\), \(b\). 你需要选择一个区间\([l,r]\),使得\(a_l+-+a_r>=0\)且\(b_l+-+ ...
- Spark从入门到放弃---RDD
什么是Spark? 关于Spark具体的定义,大家可以去阅读官网或者百度关于Spark的词条,在此不再赘述.从一个野生程序猿的角度去理解,作为大数据时代的一个准王者,Spark是一款主流的高性能分布式 ...
- Spring注解驱动开发04(给容器中注册组件的方式)
给容器中注册组件的方式 1. 组件注解标注 + 包扫描(适用于自己写的类) //控制层组件 @Controller public class PersonController { } //业务逻辑层组 ...
- oracle创建用户操作
打开命令提示框输入以下内容 1.输入:sqlplus /nolog //进入oralce控制台2.输入:conn /as sysdba //以管理员权限登录3.输入:create user abc i ...