聊聊@Validated和@Valid注解的底层实现

公众号:Hoeller,高质量底层分析文章、精品经典面试题、个人联系方式

对于这两个注解的作用和区别以及底层实现原理,我在写这篇文章之前理解是比较模糊的,这两个注解似乎都跟验证有关系,但他们之间的区别是什么?这篇文章我就来尝试分析一下。

@Valid

@Valid注解是JSR303中所定义的,这里顺便介绍一下JSP和JCP的区别,JCP表示Java Community Process,是一个用来制定Java规范的组织,组织中有很多SUN公司的技术大牛,而一个规范的制定,就需要先提交JSR,也就是Java Specification Request,翻译过来就是Java规范请求,一个JSR经过一系列阶段后才能成为最终的规范。

所以,在JSR303规范中就定义了@Valid注解,以及@NotNull、@Max、@NotEmpty等注解,这些注解都定义在了jakarta.validation-api-version.jar中,在Java中,某个规范一般来说就是对应一堆接口或注解,开发者在使用时直接使用接口或注解就可以了,但是前提是需要具体的实现者来实现,比如Servlet规范中的HttpServletRequest,也是一个接口,我们在项目中之所以能使用它,是因为Tomcat实现了Servlet规范,也就是,Tomcat提供了实现HttpServletRequest接口的具体实现类,所以对于@Valid注解也是一样的,也需要有实现者来实现,而比较著名的就是hibernate-validator了,比如在Spring Boot中,当我们引入了spring-boot-starter-validation时,就间接的引入了hibernate-validator

因此,当我们在开发时,我们直接使用规范中的注解就可以了,比如 当访问/hello接口时,Spring MVC就会对传进来的User对象进行验证,验证逻辑的底层实现在ModelAttributeMethodProcessor类的resolveArgument()方法中,它会先根据请求参数生成出一个User对象,然后再调用validateIfApplicable()方法验证User对象中各个属性是否符合所定义的规则:

validateIfApplicable()中的binder参数携带了User对象,parameter参数表示方法参数,在validateIfApplicable()方法中会先取出方法参数前定义的所有注解,然后遍历每个注解,判断某个注解是不是@Valid注解,如果是,则进行验证。

在进行验证时,会先获取出一个Validator对象,而Validator也是JSR303规范中定义的一个接口,表示验证器,在Spring Boot中获取的就是Hibernate中所实现的ValidatorImpl对象: 那么Spring Boot是如何知道ValidatorImpl的存在的呢,答案是SPI机制 ,在hibernate-validator的jar包中存在META-INF/services/jakarta.validation.spi.ValidationProvider文件,文件的内容为org.hibernate.validator.HibernateValidator 顾名思义,这个文件就是用来提供验证器的,在Spring Boot中就利用SPI机制找到这个文件及其内容,从而找到HibernateValidator,从而构造出ValidatorImpl对象。

具体ValidatorImpl对象是如何验证User对象的,本文就不展开了,原理上就是获取属性的值以及属性上所定义的各种规则,然后判断属性值是否符合规则。

到此,我们可以发现@Valid注解之所以能够生效,其实就是SPI机制在起作用,Spring Boot通过SPI机制拿到Hibernate所提供的验证器,从而进行验证。

那么当我们把@Valid注解写在某个Service中的某个方法参数前,它能起作用吗?比如: 答案是不能的,Controller中的方法参数前的@Valid注解之所以能起作用,是因为Spring MVC在执行方法时会主动去找验证器并进行参数验证,但是当Controller调用Service的某个方法时并不会去找验证器进行验证,而是直接调用Service中的方法,从而忽略了@Valid注解的存在,除非...

@Validated

除非在Service上加上@Validated注解,而@Validated注解并不是JSR303中所定义的,而是Spring提供的。

当某个Bean加了@Validated注解后,Spring在执行该Bean的方法时就会有额外处理,也就是对方法参数前加了@Valid注解的参数进行验证,而这个额外处理其底层原理就是AOP

在Spring Boot中有一个自动配置类: 这个配置类中定义了两个Bean,其中BeanMethodValidationPostProcessor这个Bean就提供了一个切面,在它的afterPropertiesSet()方法中定义了切点和切面逻辑: 从代码可以看出,切点就是@Validated注解,也就是只要一个Bean被@Validated注解标记了,那么这个Bean就符合这个切点,而对应的切面逻辑就是MethodValidationInterceptor,也就是说,一个加了@Validated注解的Bean,在执行方法时会先执行MethodValidationInterceptor中invoke()方法,并且在构造MethodValidationInterceptor时会传入一个验证器Validator对象(和上文提到的验证器对象是同样的,最终也是ValidatorImpl对象来进行验证),而在invoke()方法中就会利用验证器对象对当前正在执行的方法进行参数验证。

所以@Validated注解本质就是一个切面,使得在执行方法时能够触发去找验证器并对参数进行验证。

总结

以上就是我对@Valid注解和@Validated注解的理解,再总结一下:

  1. @Valid注解是JSR中定义的Java规范,Hibernate对这个注解进行了实现,并且在Spring MVC中,在执行Controller中的方法时会尝试获取验证器并对参数进行验证。
  2. @Validated注解是Spring提供的,意图是能够让@Valid注解在更多的地方使用并生效,@Validated注解本质上就是一个切面,使得在执行非Controller的方法时能够触发对@Valid等注解的验证。

本文只分析了@Validated注解和@Valid注解,对于@NotNull、@Max、@NotEmpty等注解留着后续再分析吧,如果有收获记得点赞、分享哦!!!

欢迎大家留言讨论,我是爱读源码的大都督周瑜,欢迎关注我的公众号:Hoeller。

相关推荐
Chrikk24 分钟前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*27 分钟前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue27 分钟前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man30 分钟前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟31 分钟前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity1 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天2 小时前
java的threadlocal为何内存泄漏
java
caridle2 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^2 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋32 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx