死磕Spring Boot Validation校验
<h3 id="一基本介绍">一、基本介绍</h3><p>SpringBoot提供了方便的validation主要对输入数据进行校验,确保数据符合预期规则,是保证应用健壮性的重要手段,<br>
1、Bean Validation:基于 JSR-380 (Bean Validation 2.0) 规范、<br>
2、Hibernate Validator:最流行的实现<br>
3、Spring 集成:通过 @Valid 或 @Validated 注解触发验证<br>
怎么使用就不介绍了,包含如何自定义注解进行校验,分组验证,处理验证错误</p>
<h3 id="二javaxvalidation">二、javax.validation</h3>
<p>这里项目jdk为1.8,所使用的包名为javax.validation,之后的版本变更为jakarta.validation<br>
这个包为Jakarta EE平台的基础核心包之一,提供验证bean标准的API,<br>
总入口为Validation类,作为标准的api,需要暴露接口供其他包进行接入,接口为ValidationProvider<br>
<img src="https://img2024.cnblogs.com/blog/1597479/202511/1597479-20251125154153340-67243060.png" alt="image" loading="lazy"><br>
ValidationProvider通过ValidationProviderResolver进行处理,除此之外,javax.validation提供了默认的处理器DefaultValidationProviderResolver<br>
会通过SPI机制ServiceLoader加载META-INF/services/<br>
如果未加载到则会抛出异常,否则会取第一个ValidationProvider<br>
<img src="https://img2024.cnblogs.com/blog/1597479/202511/1597479-20251125155028373-1260054926.png" alt="image" loading="lazy"><br>
最终通过configure生成javax.validation.Configuration<br>
Configuration也提供了非常多的接口层定义,需要实现buildValidatorFactory,再通过ValidatorFactory.getValidator进行校验<br>
javax.validation提供了一些基础的校验注解,具体校验规则也需要单独实现<br>
<img src="https://img2024.cnblogs.com/blog/1597479/202511/1597479-20251125160940909-1807530018.png" alt="image" loading="lazy"></p>
<h3 id="三hibernate实现">三、hibernate实现</h3>
<p>首先在META-INF/services目录下申明javax.validation.spi.ValidationProvider为org.hibernate.validator.HibernateValidator<br>
<img src="https://img2024.cnblogs.com/blog/1597479/202511/1597479-20251125161916046-596387653.png" alt="image" loading="lazy"><br>
HibernateValidator生成的configuration为HibernateValidatorConfiguration<br>
<img src="https://img2024.cnblogs.com/blog/1597479/202511/1597479-20251125162603897-1279342036.png" alt="image" loading="lazy"><br>
ValidatorFactory的实现为ValidatorFactoryImpl<br>
其中含有几个重要的属性</p>
<h4 id="1constraintvalidatorfactory">1、ConstraintValidatorFactory</h4>
<p>负责ConstraintValidator的创建和生命周期,通过工厂获取某个校验的ConstraintValidator实例,如果是spring项目,使用的是SpringConstraintValidatorFactory有springframework负责实现</p>
<h4 id="2校验逻辑">2、校验逻辑</h4>
<p>直到开始校验时才会执行Validator.validate方法<br>
<img src="https://img2024.cnblogs.com/blog/1597479/202511/1597479-20251126094859275-1826969173.png" alt="image" loading="lazy"><br>
这里以分组校验对象为例,Validator也提供了很多种灵活的校验,包括校验单独的某个属性<br>
其中BeanMetaData主要通过AnnotationMetaDataProvoder进行注解的元数据获取,主要思路为根据constraintHelper.isConstraintAnnotation是否当前类含有校验属性的注解Constraint.class,因为基本上每个校验注解里面都有@Constraint<br>
如果没有任何约束条件,则会直接结束,同时,BeanMetaData进行了缓存,下一次校验同类型的时候直接从缓存获取metaData<br>
紧接着会对校验的组进行排序,每次校验可以支持单个或者多个,如果未指定,默认是javax.validation.groups.Default<br>
最后会执行validateInContext进行校验,其中短路验证shouldFailFast,是hibernate专有的,如果开启了这个属性,遇到验证失败的则会直接结束,不再往下执行<br>
这里就会用到提供的接口所有实现ConstraintValidator,调用isValid方法<br>
ConstraintTree#validateSingleConstraint<br>
<img src="https://img2024.cnblogs.com/blog/1597479/202511/1597479-20251126104158834-1747635351.png" alt="image" loading="lazy"><br>
如果校验失败,开始构建约束违反的消息,主要处理类在AnnotationDescriptor#getMandatoryAttribute,主要获取注解的message属性<br>
如果message开头不是{,而是自己定义的比如姓名不能为空,则会直接返回message,否则,会通过AbstractMessageInterpolator#interpolateMessage将消息通过本地语言设置进行解析<br>
<img src="https://img2024.cnblogs.com/blog/1597479/202511/1597479-20251126110905990-214088347.png" alt="image" loading="lazy"><br>
在resource下面进行提取<br>
<img src="https://img2024.cnblogs.com/blog/1597479/202511/1597479-20251126112435800-1837850381.png" alt="image" loading="lazy"><br>
最好将校验失败的结果放入Set<constraintviolation>中<br>
至此,这个校验过程就算结束了<br>
可以单独申明Hiberator的校验器,提取为公用的工具使用</constraintviolation></p>
<pre><code>/**
* Hibernate校验器
*/
private static Validator hibernateValidator = Validation.byProvider(HibernateValidator.class).
configure().failFast(true).buildValidatorFactory().getValidator();
</code></pre>
<pre><code>/**
* Hibernate校验器
*
* @param obj 被校验实体或字段
* @param detailError 是否输出字段等详细信息
* @param groups 校验组
*/
public static void validate(Object obj, boolean detailError, Class<?>... groups) throws ValidationException {
Set<ConstraintViolation<Object>> constraintViolations = hibernateValidator.validate(obj, groups);
//判断校验返回的错误信息集合是否为空
if (CollectionUtils.isEmpty(constraintViolations)) {
return;
}
ConstraintViolation<Object> constraintViolation = constraintViolations.iterator().next();
String errorMsg = constraintViolation.getMessage();
Object invalidateValue = constraintViolation.getInvalidValue();
Object propertyPath = constraintViolation.getPropertyPath();
log.error("字段【{}】校验失败,其值为:\"{}\",不符合规则【{}】", propertyPath, invalidateValue, errorMsg);
if (detailError) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("字段");
if (propertyPath != null) {
stringBuilder.append("【");
stringBuilder.append(propertyPath);
stringBuilder.append("】");
}
stringBuilder.append("校验失败,其值为:\"");
stringBuilder.append(invalidateValue);
stringBuilder.append("\",不符合规则【");
stringBuilder.append(errorMsg);
stringBuilder.append("】");
errorMsg = stringBuilder.toString();
}
throw new BaseRuntimeException(99999, errorMsg);
}
</code></pre>
<h3 id="四springboot项目校验">四、SpringBoot项目校验</h3>
<p>在spring-boot-autoconfigure包下面提供了非常多的自动配置,validation也是其中的一环,主配置类为ValidationAutoConfiguration<br>
<img src="https://img2024.cnblogs.com/blog/1597479/202511/1597479-20251126135748770-2100257955.png" alt="image" loading="lazy"><br>
@ConditionalOnClass(ExecutableValidator.class)<br>
@ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider")<br>
这两个注解就不必多介绍了,第一个需要可执行的校验实例,而这个如果引入了hiberate,则是ValidatorImpl实现了ExecutableValidator<br>
第二个也一样,如果引入了hiberate,javax.validation.spi.ValidationProvider为org.hibernate.validator.HibernateValidator<br>
其中PrimaryDefaultValidatorPostProcessor会在@Confuguration将ValidationAutoConfiguration里面的@Bean方法注入到BeanDefinition之后,判断是否已经存在prifary的org.springframework.validation.Validator bean类型<br>
如果不存在,则会将defaultValidator设置为primary<br>
springboot默认自动配置的javax.validation.Validator为LocalValidatorFactoryBean,值得注意的是这个应该使用的类似装饰器模式,在里面申明了ValidatorFactory,通过ValidatorFactory来得到具体执行的Validator,如果引用了hibernate,则执行器为ValidatorImpl,因为将Validator注入到bean了,如果需要显示的调用,我们可以执行引用bean</p>
<pre><code> @Autowired
private Validator validator;
</code></pre>
<p>到这里我们只是将Validator注入到spring容器当中了,只能显示的调用validator.validate方法,但是spring有aop可不建议这么做<br>
第二个自动装配的是MethodValidationPostProcessor,之前我们常用的就是在controller接口层添加@Validated注解,再添加各种限制条件注解,MethodValidationPostProcessor负责处理这个逻辑<br>
由于继承了AbstractAdvisingBeanPostProcessor,可以自动为匹配的bean创建aop代理,将配置的advice应用到目标bean上<br>
首先在InitializingBeand的afterPropertiesSet会优先定义pointcut和advisor,切入点是含有@Validated注解的类或者方法</p>
<pre><code>Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
</code></pre>
<p>其中切面的拦截器是MethodValidationInterceptor,传入validator,一般常用的controller里面当有请求过来时,会优先走下面的拦截器进行参数的验证方法<br>
<img src="https://img2024.cnblogs.com/blog/1597479/202511/1597479-20251126150518713-1282701538.png" alt="image" loading="lazy"><br>
接着由于实现了BeanPostProcessor,执行完开始执行afterPropertiesSet方法开始执行postProcessAfterInitialization,这个方法会将申明的advised添加bean的切面里面</p>
<p>像MethodValidationPostProcessor这种已经相当于模板方法了,像@Async异步任务和上面的是同理,@Scheduled定时调度也差不多这个道理实现的切面</p><br><br>
来源:https://www.cnblogs.com/LiuFqiang/p/19269012
頁:
[1]