公众号: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注解的理解,再总结一下:
- @Valid注解是JSR中定义的Java规范,Hibernate对这个注解进行了实现,并且在Spring MVC中,在执行Controller中的方法时会尝试获取验证器并对参数进行验证。
- @Validated注解是Spring提供的,意图是能够让@Valid注解在更多的地方使用并生效,@Validated注解本质上就是一个切面,使得在执行非Controller的方法时能够触发对@Valid等注解的验证。
本文只分析了@Validated注解和@Valid注解,对于@NotNull、@Max、@NotEmpty等注解留着后续再分析吧,如果有收获记得点赞、分享哦!!!
欢迎大家留言讨论,我是爱读源码的大都督周瑜,欢迎关注我的公众号:Hoeller。