Hibernate-Validator的使用(一)

news2024/11/24 21:03:21

文章目录

  • 学习建议
  • 全部的约束注解
    • 关于@NotEmpty和@NotBlank的注意事项
    • 关于@Size的注意事项
  • 约束注解可以放的位置
    • JavaBean
      • Validator接口
      • 约束注解的继承性
    • 普通方法和构造方法入参和返回值
      • ExecutableValidator 接口
      • 约束注解的继承性
  • 错误信息国际化显示
    • 定义message使用的消息key
    • 定义ValidationMessages文件
    • 验证错误消息国际化是否生效
    • RestEasy是如何帮我们做到的呢?
  • Hibernate-Validator初始化过程
  • 备注

学习建议

建议结合Hibernate-Validator和Jakarta Bean Validation规范学习。
如果忘记某个注解或功能如何使用了,可以快速参考Hibernate-Validator官方提供的示例
在这里插入图片描述
官方代码非常清晰,以每一章为一个目录
在这里插入图片描述

全部的约束注解

可以从jakarta-bean-validation-spec-3.0规范中找到全部注解以及约束条件

关于@NotEmpty和@NotBlank的注意事项

  • 二者都会校验是否为null,所以不需要再次添加@NotNull

关于@Size的注意事项

  • 此注解是针对字符串和集合、数组、Map类型的,对于数值类型的校验使用@Max和@Min

约束注解可以放的位置

JavaBean

  1. field-level

    public class Car {
    
    	@NotNull
    	private String manufacturer;
    
    	@AssertTrue
    	private boolean isRegistered;
    
    	public Car(String manufacturer, boolean isRegistered) {
    		this.manufacturer = manufacturer;
    		this.isRegistered = isRegistered;
    	}
    
    	//getters and setters...
    }
    
  2. property-level,这种方式和上面效果一样,必须是放到get方法上,不是set方法

    The property’s getter method has to be annotated, not its setter. That way also read-only properties can be constrained which have no setter method

    public class Car {
    
    	private String manufacturer;
    
    	private boolean isRegistered;
    
    	public Car(String manufacturer, boolean isRegistered) {
    		this.manufacturer = manufacturer;
    		this.isRegistered = isRegistered;
    	}
    
    	@NotNull
    	public String getManufacturer() {
    		return manufacturer;
    	}
    
    	public void setManufacturer(String manufacturer) {
    		this.manufacturer = manufacturer;
    	}
    
    	@AssertTrue
    	public boolean isRegistered() {
    		return isRegistered;
    	}
    
    	public void setRegistered(boolean isRegistered) {
    		this.isRegistered = isRegistered;
    	}
    }
    
  3. constainer-level,包括List、Set、Map、Optional

  4. Object graphs,一个JavaBean的属性是另外一个JavaBean,通过在需要校验的Bean上使用@Valid注解即可实现校验

    public class Car {
    
    	private List<@NotNull @Valid Person> passengers = new ArrayList<Person>();
    }
    

    当然也可以这样写,不过官方不建议

    public class Car {
    
    	 @Valid private List<@NotNull Person> passengers = new ArrayList<Person>();
    }
    

    In versions prior to 6, Hibernate Validator supported cascaded validation for a subset of container elements and it was implemented at the container level (e.g. you would use @Valid private List to enable cascaded validation for Person).

    This is still supported but is not recommended. Please use container element level @Valid annotations instead as it is more expressive.

  5. class-level,即约束注解是放在类上的,而不是具体的属性上的

Validator接口

以上对JavaBean以及其属性的校验,是由Validator接口负责校验,它有如下几个方法

Validator#validate()
Validator#validateProperty()
Validator#validateValue()

具体使用可参考官方示例或者官方文档中关于Validator的文档中的示例代码

约束注解的继承性

约束注解是可继承,当在父类或者接口中添加了约束注解,则子类及其实现会自动继承该约束注解

When a class implements an interface or extends another class, all constraint annotations declared on the super-type apply in the same manner as the constraints specified on the class itself

普通方法和构造方法入参和返回值

  • 约束注解可以放在普通方法的入参前面,对入参进行校验
  • 约束注解可以放到普通方法之上,代表对该方法的返回值进行校验
  • 约束注解可以放到构造方法的入参前面,对入参进行校验
  • 约束注解可以放到构造方法之上,代表对构造方法的返回值进行校验

例如

public class RentalStation {

	public RentalStation(@NotNull String name) {
	}

	public void rentCar(
			@NotNull Customer customer,
			@NotNull @Future Date startDate,
			@Min(1) int durationInDays) {
	}
}
public class RentalStation {

	@ValidRentalStation
	public RentalStation() {
	}

	@NotNull
	@Size(min = 1)
	public List<@NotNull Customer> getCustomers() {
		return null;
	}
}

ExecutableValidator 接口

对普通方法、构造方法的入参和返回值的校验由ExecutableValidator负责。它有以下几个方法

validateParameters()
validateReturnValue()
validateConstructorParameters()
validateConstructorReturnValue()

具体使用可参考官方示例或者官方文档中关于ExecutableValidator的文档中的示例代码

约束注解的继承性

  • 对于入参的校验,已经在超类中声明里约束注解时,则子类不能覆盖约束注解.例如下面这样
    public interface Vehicle {
    
    void drive(@Max(75) int speedInMph);
    }
    
    public class Car implements Vehicle {
    
        @Override
        public void drive(@Max(55) int speedInMph) {
            //...
        }
    }
    
  • 对于入参的校验,如果一个方法覆盖或实现了在多个并行超类型中声明的方法(例如,一个类实现了两个接口中名字相同的方法),则不能在任何涉及的方法中指定参数约束类型。例如下面这样
    
    public interface Vehicle {
    
        void drive(@Max(75) int speedInMph);
    }
    
    public interface Car {
    
        void drive(int speedInMph);
    }
    
    public class RacingCar implements Car, Vehicle {
    
        @Override
        public void drive(int speedInMph) {
            //...
        }
    }
    
  • 对于返回值的校验,则可以对超类中的约束注解进行覆盖

有关上述更详细的解释移步官方文档

错误信息国际化显示

每一个注解上都有一个message属性,当校验失败时,会抛出此信息。平时开发中如果我们不需要做国际化处理,那么直接在message属性后面写对应的error message即可。

但是如果我们的项目的工程师团队涉及到多个国家,那么当不同地区的工程师排查问题时,查看到的错误日志如果是国际化的,那么对于排查问题的工程师就非常友好了。

有过Web开发经验的Java工程师可能都了解ResourceBundle这个类,这个类就是负责国际化的,一个简单的例子

// language和country实际中可以在Accept-Lanuage请求头中获取
Locale locale = new Locale(language, country);
// 根据Local来获取不同的ResourceBundle
ResourceBundle resourceBundle = ResourceBundle.getBundle("message", locale, I18nUtil.getResourceBundleClassLoader());
// 消息的key
return resourceBundle.getString("hello");

就三行代码,我们在项目的resource目录下建立不同国家的message_languageCode_countryCode.properties文件,

  • message_zh_CN.properties
  • message_en_US.properties

Hibernate-Validator也是使用了相同的方法,不过i18n配置文件的前缀需要使用ValidationMessages,为什么呢?因为这是Bean Validation规范里定的。
如果你就不想使用ValidationMessages这个名字,那么你也可以自己实现MessageInterpolator。具体实现的细节可以参考Hibernate-Validator官方文档和Bean Validation规范文档。这一部分不在本篇文章里做过多讨论

定义message使用的消息key

key用{}括起来

@Getter
@Setter
public class Person {

    @Size(min = 3, max = 5, message = "{userName.invalid}")
    private String userName;
    
    @Max(value = 10, message = "{userAge.invalid}")
    private Integer userAge;
}

定义ValidationMessages文件

这里定义两个文件ValidationMessages_zh_CN.properties和ValidationMessages_en_US.properties来做测试。二者均放在resource目录下

  1. ValidationMessages_en_US.properties的内容
    	userName.invalid= userName ${validatedValue} is invalid
        userAge.invalid= max value is {value}, but current value is ${validatedValue}
    
  2. ValidationMessages_zh_CN.properties的内容
    	userName.invalid=\u7528\u6237\u540d ${validatedValue} \u662f\u975e\u6cd5\u7684
        userAge.invalid=\u6700\u5927\u503c\u662f {value}, \u4f46\u662f\u5f53\u524d\u503c\u662f ${validatedValue}
    
    汉字部分需要转成Unicode

在每个key后面的实际内容中,上述例子有两个特殊值,

  1. ${validatedValue}代表的是当前值,如果要读取当前值,必须是前面这个变量名。里面也可以写EL表达式
  2. {value} 是具体约束注解的属性,可能是value,也可能是min,max这种

Message parameters are string literals enclosed in {},
while message expressions are string literals enclosed in ${}

如果看一下Hibernate-Validator源码会发现,源码文件中也包含了很多ValidationMessages文件,那么我们自定义了之后,会不会覆盖了呢?
一般来说,Hibernate-Validator里的ValidationMessages文件内容都是约束注解的默认信息,对于我们自定义的的key是不会有冲突的。关于查找key的规则查看Hibernate-Validator关于Message interpolation的文档和Bean Validation关于Message interpolation的文档

验证错误消息国际化是否生效

本篇文章使用Restful Web Service的框架不是SpringMVC,而是RestEasy.
启动项目之后,curl访问

curl --header "Accept-Language: en-US" --json '{"userName":"tty", "userAge":40}' http://localhost:8080/validation-demo/valid/inline
curl --header "Accept-Language: zh-CN" --json '{"userName":"tty", "userAge":40}' http://localhost:8080/validation-demo/valid/inline

在这里插入图片描述
可以看到,返回的错误信息是根据我们的Accept-Language的请求头来国际化错误信息的

RestEasy是如何帮我们做到的呢?

Hibernate-Validator里仅仅是告诉我们如何使用国际化,但是根据请求头的Accept-Language不通过的内容进行切换,它是没有实现的。带着这个疑问,决定看一下RestEasy的源码
根据RestEasy官方文档的Validaton部分,RestEasy集成Hibernate-Validator的工作是在resteasy-validator-provider 这个jar包下完成的。通过查看

  1. MessageInterpolator该接口的实现类,发现对应的RestEasy实现类为LocaleSpecificMessageInterpolator

  2. 发现org.jboss.resteasy.plugins.validation.GeneralValidatorImpl#getValidator方法实例化了LocaleSpecificMessageInterpolator

    在这里插入图片描述
    在这里插入图片描述
    getLocale里的逻辑就是根据Accept-Language请求头获得Locale,然后把这个Locale和当前的MessageInterpolator传进去,达到改变当前MessageInterpolator的locale的目的,这里RestEasy做的很聪明。目的只是想要改变locale即可,那么就用同一种MessageInterpolator类型的实现LocaleSpecificMessageInterpolator再把现有的MessageInterpolator wrap一下就好了,通过构造方法设置locale。

    注意下面这两行很关键,我们知道Validator的实例是由ValidatorFactory生成的.上面已经根据不同locale生成了一个新的MessageInterpolator,那么现在需要ValidatorFactory使用新的MessageInterpolator生成Validator实例。
    usingContext()方法就是干这个事情的
    Bean Validation文档

    ValidatorContext returned by usingContext() can be used to customize the state in which the Validator must be initialized. This is used to customize the MessageInterpolator, the TraversableResolver, the ParameterNameProvider, the ClockProvider or the ConstraintValidatorFactory

    Hibernate-Validator文档

    When working with a configured validator factory it can occasionally be required to apply a different configuration to a single Validator instance. Example 9.28, “Configuring a Validator instance via usingContext()” shows how this can be achieved by calling ValidatorFactory#usingContext()

3.获得Validator实例之后再调用对应的方法,其中一个截图
在这里插入图片描述
4. 那么GeneralValidatorImpl又是在哪里实例化的呢?查看其构造方法被调用的位置
在这里插入图片描述
在这里插入图片描述
根据上面两张截图,ValidatorContextResolver是一个Provider,实现了ContextResolver接口,所以在收到请求时会进入此Provider,从而完成实例化新的MessageInterpolator,并获取对应Validator接口实例,进而完成校验工作。有关ContextResolver的更多信息参考JAX-RS规范

Hibernate-Validator初始化过程

因为项目中的CDI是使用Weld CDI,Weld CDI初始化过程中会加载inject的spi,由hiernate-validator-cdi里的spi触发
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
RestEasy同样会生成一个ValidatorFactory实例
在这里插入图片描述

备注

  • 源码仓库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/114680.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

模仿现实生活中的通讯录(2)

距离第一篇已经过去很久&#xff0c;我之所以暂时放下通讯录&#xff0c;是因为学业颇多&#xff0c;无暇顾及。现在放假已经有一段时间了&#xff0c;脱离每天忙碌的生活后&#xff0c;我只想享受一下整日无事&#xff0c;浑浑噩噩过一天的感觉&#xff0c;只不过差点没收得住…

leetcode150/155. 逆波兰表达式求值、最小栈;剑指 Offer 31. 栈的压入、弹出序列

目录 题目链接与简介 题目一 1.算法思路 2.总结心得 3.可执行代码 题目二 1.算法思路 2.总结心得 3.可执行代码 题目三 1.算法思路 2.总结心得 3.可执行代码 题目链接与简介 1.逆波兰表达式求值 2.最小栈 3.剑指 Offer 31. 栈的压入、弹出序列 题目一 LeetCod…

软件测试 -- 进阶 8 软件测试流程和过程

盖天下之事&#xff0c;不难于立法&#xff0c;而难于法之必行&#xff1b;不难于听言&#xff0c;而难于言之必效。-- 明 张居正 释译&#xff1a;天下大事&#xff0c;困难的不在于要制定什么法律&#xff0c;而在于立了法就一定要执行&#xff1b;重要的不在于说什么&a…

【实时数仓】DWS层之地区主题表(FlinkSQL)、关键词主题表(FlinkSQL)

文章目录一 DWS层-地区主题表(FlinkSQL)1 分组开窗和聚合计算&#xff08;1&#xff09;分组窗口&#xff08;2&#xff09;选择分组窗口的开始和结束时间戳&#xff08;3&#xff09;系统内置函数&#xff08;4&#xff09;完整代码2 将动态表转换为流&#xff0c;写入ClickHo…

计算机基础(五):C语言的程序的处理过程

一段C语言程序 打开任何一个C语言的教程&#xff0c;首先自然是展示一段 Hello World 的程序&#xff0c;类似于下面这段&#xff1a; #include <stdio.h>int main() {/* 我的第一个 C 程序 */printf("Hello, World! \n");return 0; }运行上面这段程序也很简…

Qt中的数据库(简单使用)

在Qt中支持对数据库的操作 Qt中数据库的类有&#xff1a; 驱动层&#xff1a;为具体的数据库和SQL接口层之间提供底层的桥梁SQL层&#xff1a;提供对数据库的访问 QSqlDateBase类用来创建连接QSqlQuery可以使用SQL语句实现交互用户接口层&#xff1a;实现将数据库中的数据链接…

36. 卷积神经网络(LeNet)

通过之前几节&#xff0c;我们学习了构建一个完整卷积神经网络的所需组件。 回想一下&#xff0c;之前我们将softmax回归模型和多层感知机模型应用于Fashion-MNIST数据集中的服装图片&#xff0c;为了能够应用softmax回归和多层感知机&#xff1a; 我们首先将每个大小为的图像…

【Web开发】Python实现Web服务器(Ubuntu下Flask使用MySQL数据库)

&#x1f37a;基于Python的Web服务器系列相关文章编写如下&#x1f37a;&#xff1a; &#x1f388;【Web开发】Python实现Web服务器&#xff08;Flask快速入门&#xff09;&#x1f388;&#x1f388;【Web开发】Python实现Web服务器&#xff08;Flask案例测试&#xff09;&a…

大数据-玩转数据-Kafka实战

一、kafka使用要点 要点一&#xff1a;Producer即生产者&#xff0c;向Kafka集群发送消息&#xff0c;在发送消息之前&#xff0c;会对消息进行分类&#xff0c;即Topic&#xff0c;topic1&#xff0c;topic2。Topic即主题&#xff0c;通过对消息指定主题可以将消息分类&#…

工具-能写会看正则表达式【强烈建议收藏!】

正则表达式,很常用的一个技能点,但是一般的开发流程中都是这样的: 需要验证数据上网搜一下正则表达式CV 搞定!!!今天有时间回看了一下文档,简单整理了一下里面需要注意的点,并且通过分析几个常见的正则表达式,下次遇到正则争取不再只依靠 CV 大法! 基础部分 基本语法 …

初识指针(9)

目录 1、指针是什么&#xff1f; 2、指针和指针类型 1、指针- 整数 2、指针的解引用 3、野指针 1、野指针成因 2、如何规避野指针 4、指针运算 1、指针- 整数 2、指针- 指针 3、指针的关系运算 5、指针和数组 6、二级指针 7、指针数组 1、指针是什么&#xff1f;…

04-Hystrix

服务熔断Hystrix 1. Hystrix是什么 分布式系统环境下&#xff0c;服务间类似依赖非常常见&#xff0c;一个业务调用通常依赖多个基础服务。如下图&#xff0c;对于同步调用&#xff0c;当库存服务不可用时&#xff0c;商品服务请求线程被阻塞&#xff0c;当有大批量请求调用库…

SpringBoot-2 读取properties;自动加载127个类原理总结;全部加载,按需配置

读取properties 方式一&#xff1a;非配置类填写&#xff1a;ComponentConfigurationProperties 1)建立bean&#xff1a; /只有在容器中的组件才拥有springboot提供的强大功能 Component ConfigurationProperties(prefix "mycar") public class Car {private Stri…

【机器学习】模型评估与选择

模型评估与选择 目录一、评估方法1、留出法2、交叉验证法3、自助法二、性能度量1、错误率与准确率2、查准率、查全率阈值对查准率、查全率的影响3、F1度量&#xff08;基于查准率与查全率的调和平均&#xff09;4、P-R Curve5、ROC CurvePRC和ROC的选用准则PRC和ROC的差异6、代…

python 中文转带音调的拼音

python 中文转带音调的拼音 前言python 中文转带音调的拼音1、1.1 安装pinyin模块1.2 试验1.3 效果图1.4 代码实现前言 今天整理中药材,每个药材上标上带音调的拼音,查了些,有的易形成乱码,如Shān Mi Dōnɡ,想到python自已动手转算了 python 中文转带音调的拼音 1、 …

(五)汇编语言——[bx]和loop指令

目录 [...]与&#xff08;...&#xff09; [...] &#xff08;...&#xff09; idata Loop指令 段前缀 总结 [...]与&#xff08;...&#xff09; [...] 这个我们其实见过&#xff0c;代表的是一个内存单元&#xff0c;段地址在DS中&#xff0c;偏移地址就是[bx]。 &am…

《图解TCP/IP》阅读笔记(第七章 7.4)—— RIP 路由信息协议

7.4 RIP RIP&#xff08;Routing Information Protocol&#xff0c;路由信息协议&#xff09;&#xff0c;是一种距离向量算法&#xff0c;广泛用于LAN。 该协议将路由控制信息定期&#xff08;30秒一次&#xff09;向全网广播。如果没有收到路由控制信息&#xff0c;连接就会…

【1739. 放置盒子】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 有一个立方体房间&#xff0c;其长度、宽度和高度都等于 n 个单位。请你在房间里放置 n 个盒子&#xff0c;每个盒子都是一个单位边长的立方体。放置规则如下&#xff1a; 你可以把盒子放在地板上的…

筛法(线性筛,厄拉多塞筛)

在前前前前前前…的博客中,我们主要谈了欧拉筛和埃氏筛. 今天我们主要来聊一聊线性筛和厄拉多塞筛(其实和埃氏筛和欧拉筛本质上没区别哎).其实在这两种筛法中厄拉多塞筛最好懂(就连本蒟蒻一看代码就明白了…别看这个名字,容易糊弄人) 首先是厄拉多塞筛"粉墨登场"::…

HRTransNet阅读理解

E. Dual-direction short connection fusion module HRFormer applies transformer blocks to enlarge receptive field of fused feature Frs, and uses exchange units to absorb the merits of multi-scales features. The process is described as: HRFormer使用TRM块来扩…