• Validation参数校验


    一、Validation参数校验准备工作

    参考学习资料: https://juejin.cn/post/6856541106626363399

    1.1需要的依赖:

    ```xml
    
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.6.10version>
        <relativePath/> 
    parent>
    
            
    
            
            
            
            
            
    
    <dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-validationartifactId>
    dependency>
    
    ```
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    1.2 简单的约束注解

    注解功能
    @AssertFalse可以为null,如果不为null的话必须为false
    @AssertTrue可以为null,如果不为null的话必须为true
    @DecimalMax设置不能超过最大值
    @DecimalMin设置不能超过最小值
    @Digits设置必须是数字且数字整数的位数和小数的位数必须在指定范围内
    @Future日期必须在当前日期的未来
    @Past日期必须在当前日期的过去
    @Max最大不得超过此最大值
    @Min最大不得小于此最小值
    @NotNull不能为null,可以是空
    @Null必须为null
    @Pattern必须满足指定的正则表达式
    @Size集合、数组、map等的size()值必须在指定范围内
    @Email必须是email格式
    @Length长度必须在指定范围内
    @NotBlank字符串不能为null,字符串trim()后也不能等于“”
    @NotEmpty不能为null,集合、数组、map等size()不能为0;字符串trim()后可以等于“”
    @Range值必须在指定范围内
    @URL必须是一个URL

    二、基础使用

    参考代码: tom-technology-demo中的technology-validation模块

    包含了:单个参数、对象参数、分组校验、嵌套单个校验、嵌套集合校验

    三、校验原理

    3.1 RequestBody参数校验实现原理

    spring-mvc中,RequestResponseBodyMethodProcessor是用于解析@RequestBody标注的参数以及处理@ResponseBody标注方法的返回值的。显然,执行参数校验的逻辑肯定就在解析参数的方法resolveArgument()

    public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {  
        @Override  
        public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,  
                                      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {  
      
            parameter = parameter.nestedIfOptional();  
      
            Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());  
            String name = Conventions.getVariableNameForParameter(parameter);  
      
            if (binderFactory != null) {  
                WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);  
                if (arg != null) {  
      				//validate参数校验
                    validateIfApplicable(binder, parameter);  
                    if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {  
                        throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());  
                    }  
                }  
                if (mavContainer != null) {  
                    mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());  
                }  
            }  
            return adaptArgumentIfNecessary(arg, parameter);  
        }  
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    可以看到,resolveArgument()调用了validateIfApplicable()进行参数校验。

    protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {  
      
        Annotation[] annotations = parameter.getParameterAnnotations();  
        for (Annotation ann : annotations) {  
      
            Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);  
      
            if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {  
                Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));  
                Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});  
      
                binder.validate(validationHints);  
                break;  
            }  
        }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这种场景下@Validated@Valid两个注解可以混用。接下来继续看WebDataBinder.validate()实现。

    @Override  
    public void validate(Object target, Errors errors, Object... validationHints) {  
        if (this.targetValidator != null) {  
            processConstraintViolations(  
      
                this.targetValidator.validate(target, asValidationGroups(validationHints)), errors);  
        }  
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    底层最终还是调用了Hibernate Validator进行真正的校验处理

    3.2 方法级别的参数校验实现原理

    将参数一个个平铺到方法参数中,然后在每个参数前面声明约束注解的校验方式,就是方法级别的参数校验。实际上,这种方式可用于任何Spring Bean的方法上,比如Controller/Service等。其底层实现原理就是**AOP,具体来说是通过MethodValidationPostProcessor动态注册AOP切面,然后使用MethodValidationInterceptor对切点方法织入增强**

    public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessorimplements InitializingBean {  
        @Override  
        public void afterPropertiesSet() {  
      
            Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);  
      
            this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));  
        }  
      
        protected Advice createMethodValidationAdvice(@Nullable Validator validator) {  
            return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());  
        }  
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    接着看一下MethodValidationInterceptor

    public class MethodValidationInterceptor implements MethodInterceptor {  
        @Override  
        public Object invoke(MethodInvocation invocation) throws Throwable {  
      
            if (isFactoryBeanMetadataMethod(invocation.getMethod())) {  
                return invocation.proceed();  
            }  
      
            Class<?>[] groups = determineValidationGroups(invocation);  
            ExecutableValidator execVal = this.validator.forExecutables();  
            Method methodToValidate = invocation.getMethod();  
            Set<ConstraintViolation<Object>> result;  
            try {  
      
                result = execVal.validateParameters(  
                    invocation.getThis(), methodToValidate, invocation.getArguments(), groups);  
            }  
            catch (IllegalArgumentException ex) {  
                ...  
            }  
      
            if (!result.isEmpty()) {  
                throw new ConstraintViolationException(result);  
            }  
      
            Object returnValue = invocation.proceed();  
      
            result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups);  
      
            if (!result.isEmpty()) {  
                throw new ConstraintViolationException(result);  
            }  
            return returnValue;  
        }  
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    不管是**requestBody参数校验还是方法级别的校验,最终都是调用Hibernate Validator执行校验,Spring Validation只是做了一层封装**。

  • 相关阅读:
    [附源码]Python计算机毕业设计Django病人跟踪治疗信息管理系统
    webpack知识点
    accent-color的使用
    什么是Spring
    Java中单体应用锁的局限性&分布式锁
    7-143 降价提醒机器人
    中望CAD 2025 (ZW3D2025) 简体中文修改版
    Python 教程之控制流(9)Python 中的 Switch Case(替换)
    Android 面试经历复盘整理~
    Kubernetes(k8s)— Concepts — Containers
  • 原文地址:https://blog.csdn.net/zhongxu_yuan/article/details/126215482