Spring Boot项目里对于接口参数校验,可以使用javax.validation.constraints
包下的注解来优雅的校验。比如参数长度、是否为null甚至可以使用正则表达式来校验参数格式,以及校验不通过返回的提示信息都可以通过注解进行配置,实在是方便的很。
但是本人在开发中遇到了这么几个问题:
- 某一次SpringBoot版本升级后,启动项目提示我没有·javax.validation·的依赖,一开始导入了
javax.validation-validation-api
仍旧不能启动,后来导入了hibernate-validator
的依赖就可以了。为什么? - 有的文章说,使用这个校验器要在
controller
上加@Validated注解,同时接口参数里也要加才能使用,really? @Valid
是javax包下的注解,而@Validated
是Spring的注解,在Spring Boot项目里得使用后者才行。确定?
基于以上几个问题,我翻阅了不少资料,debug好几轮源码才找到了答案,有兴趣得伙伴可以跟着我一起来看看。
Spring Boot 2.3
2.3版本的SpringBoot将不再依赖javax.validation
的包,所以,开发人员需要自行导入依赖,官方推荐的是使用自家的这个依赖。
Validation Starter no longer included in web starters
As of #19550, Web and WebFlux starters do not depend on the validation starter by default anymore. If your application is using validation features,
for example you find that javax.validation.* imports are not being resolved, you’ll need to add the starter yourself.
For Maven builds, you can do that with the following:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
看下这个dependency的依赖关系
它其实还是依赖了hibernate校验器的包,毕竟它是一个成熟的工具了,Spring当然是取其精华,不再自己搞一套了。
值得注意的是,这个jakarta.validation
又是什么鬼?怎么和javax.validation
不一样呢。打开这个包看看,其实是一样的。
javax.validation-validation-api
这个包其实就是一个api的包,里边包含了所有的注解及接口,但是没有实现,所以若是只导入了这个依赖,是不行滴
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
再往深想一步,为什么不行?
先看一下这个包里边最重要的一个类Validator
,它定义了一些接口,供实现类去实现
public interface Validator {
<T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);
<T> Set<ConstraintViolation<T>> validateProperty(T object,String propertyName,Class<?>... groups);
<T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType,
String propertyName,
Object value,
Class<?>... groups);
BeanDescriptor getConstraintsForClass(Class<?> clazz);
<T> T unwrap(Class<T> type);
ExecutableValidator forExecutables();
}
接着参数校验肯定是在执行接口Handler
前做掉的,也就是将参数封装成对象后,要进行参数校验,在这一步肯定会有类似校验器的东西(即Validator
的实现类)去执行具体的参数校验
我们debug源码看看,源码只列出核心部分,多余内容都用省略号表示
Spring MVC的请求分发
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
}
一直往下走,走到参数校验的位置
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
...
if (bindingResult == null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
this.bindRequestPara