首页 > 基础资料 博客日记

死磕Spring Boot Validation校验

2025-11-26 15:30:02基础资料围观8

这篇文章介绍了死磕Spring Boot Validation校验,分享给大家做个参考,收藏Java资料网收获更多编程知识

一、基本介绍

SpringBoot提供了方便的validation主要对输入数据进行校验,确保数据符合预期规则,是保证应用健壮性的重要手段,
1、Bean Validation:基于 JSR-380 (Bean Validation 2.0) 规范、
2、Hibernate Validator:最流行的实现
3、Spring 集成:通过 @Valid 或 @Validated 注解触发验证
怎么使用就不介绍了,包含如何自定义注解进行校验,分组验证,处理验证错误

二、javax.validation

这里项目jdk为1.8,所使用的包名为javax.validation,之后的版本变更为jakarta.validation
这个包为Jakarta EE平台的基础核心包之一,提供验证bean标准的API,
总入口为Validation类,作为标准的api,需要暴露接口供其他包进行接入,接口为ValidationProvider
image
ValidationProvider通过ValidationProviderResolver进行处理,除此之外,javax.validation提供了默认的处理器DefaultValidationProviderResolver
会通过SPI机制ServiceLoader加载META-INF/services/
如果未加载到则会抛出异常,否则会取第一个ValidationProvider
image
最终通过configure生成javax.validation.Configuration
Configuration也提供了非常多的接口层定义,需要实现buildValidatorFactory,再通过ValidatorFactory.getValidator进行校验
javax.validation提供了一些基础的校验注解,具体校验规则也需要单独实现
image

三、hibernate实现

首先在META-INF/services目录下申明javax.validation.spi.ValidationProvider为org.hibernate.validator.HibernateValidator
image
HibernateValidator生成的configuration为HibernateValidatorConfiguration
image
ValidatorFactory的实现为ValidatorFactoryImpl
其中含有几个重要的属性

1、ConstraintValidatorFactory

负责ConstraintValidator的创建和生命周期,通过工厂获取某个校验的ConstraintValidator实例,如果是spring项目,使用的是SpringConstraintValidatorFactory有springframework负责实现

2、校验逻辑

直到开始校验时才会执行Validator.validate方法
image
这里以分组校验对象为例,Validator也提供了很多种灵活的校验,包括校验单独的某个属性
其中BeanMetaData主要通过AnnotationMetaDataProvoder进行注解的元数据获取,主要思路为根据constraintHelper.isConstraintAnnotation是否当前类含有校验属性的注解Constraint.class,因为基本上每个校验注解里面都有@Constraint
如果没有任何约束条件,则会直接结束,同时,BeanMetaData进行了缓存,下一次校验同类型的时候直接从缓存获取metaData
紧接着会对校验的组进行排序,每次校验可以支持单个或者多个,如果未指定,默认是javax.validation.groups.Default
最后会执行validateInContext进行校验,其中短路验证shouldFailFast,是hibernate专有的,如果开启了这个属性,遇到验证失败的则会直接结束,不再往下执行
这里就会用到提供的接口所有实现ConstraintValidator,调用isValid方法
ConstraintTree#validateSingleConstraint
image
如果校验失败,开始构建约束违反的消息,主要处理类在AnnotationDescriptor#getMandatoryAttribute,主要获取注解的message属性
如果message开头不是{,而是自己定义的比如姓名不能为空,则会直接返回message,否则,会通过AbstractMessageInterpolator#interpolateMessage将消息通过本地语言设置进行解析
image
在resource下面进行提取
image
最好将校验失败的结果放入Set
至此,这个校验过程就算结束了
可以单独申明Hiberator的校验器,提取为公用的工具使用

/**
     * Hibernate校验器
     */
    private static Validator hibernateValidator = Validation.byProvider(HibernateValidator.class).
            configure().failFast(true).buildValidatorFactory().getValidator();
/**
     * 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);
    }

四、SpringBoot项目校验

在spring-boot-autoconfigure包下面提供了非常多的自动配置,validation也是其中的一环,主配置类为ValidationAutoConfiguration
image
@ConditionalOnClass(ExecutableValidator.class)
@ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider")
这两个注解就不必多介绍了,第一个需要可执行的校验实例,而这个如果引入了hiberate,则是ValidatorImpl实现了ExecutableValidator
第二个也一样,如果引入了hiberate,javax.validation.spi.ValidationProvider为org.hibernate.validator.HibernateValidator
其中PrimaryDefaultValidatorPostProcessor会在@Confuguration将ValidationAutoConfiguration里面的@Bean方法注入到BeanDefinition之后,判断是否已经存在prifary的org.springframework.validation.Validator bean类型
如果不存在,则会将defaultValidator设置为primary
springboot默认自动配置的javax.validation.Validator为LocalValidatorFactoryBean,值得注意的是这个应该使用的类似装饰器模式,在里面申明了ValidatorFactory,通过ValidatorFactory来得到具体执行的Validator,如果引用了hibernate,则执行器为ValidatorImpl,因为将Validator注入到bean了,如果需要显示的调用,我们可以执行引用bean

 @Autowired
 private Validator validator;

到这里我们只是将Validator注入到spring容器当中了,只能显示的调用validator.validate方法,但是spring有aop可不建议这么做
第二个自动装配的是MethodValidationPostProcessor,之前我们常用的就是在controller接口层添加@Validated注解,再添加各种限制条件注解,MethodValidationPostProcessor负责处理这个逻辑
由于继承了AbstractAdvisingBeanPostProcessor,可以自动为匹配的bean创建aop代理,将配置的advice应用到目标bean上
首先在InitializingBeand的afterPropertiesSet会优先定义pointcut和advisor,切入点是含有@Validated注解的类

Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);

其中切面的拦截器是MethodValidationInterceptor,传入validator,一般常用的controller里面当有请求过来时,会优先走下面的拦截器进行参数的验证方法
image
接着由于实现了BeanPostProcessor,执行完开始执行afterPropertiesSet方法开始执行postProcessAfterInitialization,这个方法会将申明的advised添加bean的切面里面

像MethodValidationPostProcessor这种已经相当于模板方法了,像@Async异步任务和上面的是同理,@Scheduled定时调度也差不多这个道理实现的切面


文章来源:https://www.cnblogs.com/LiuFqiang/p/19269012
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

上一篇:剑指offer-42、和为S的两个数字
下一篇:没有了

相关文章

本站推荐

标签云