SpringBoot,SpringMVC中Validator中实现参数绑定的原理和总结

【SpringMVC】springmvc中的数据校验 使用代理的方式,在参数都解析好了之后,我先校验下参数,之后再执行处理器方法。校验解析出来的参数,如果这个参数后面跟的是BindingResult,那么特定的参数解析器会将绑定结果解出来;所以可以用不同的校验组件,我们属性的就是Hibernate Validator ,如果想换校验组件;这里注意:springmvc中只是引用了校验框架,真正的校验功能,springmvc没有实现。真正干事的是校验框架,所以本篇是分析,springmvc是如何使用校验框架的。切点很好理解,就是对类的过滤,根据提供的条件。... 阅读详情
/**
 * <pre>
 *
 * 总结:
 *      1. 在SpringBoot中,参数为非复杂对象类型,默认情况下,在参数解析的时候不会进行参数校验,因为这这些简单类型不支持Bean Validation规范,而参数为复杂对象的时候,才会使用校验器对该对象进行字段校验
 *
 *
 *      简单参数如何进行校验呢? 除非手动调用Validator的API,否则就需要Spirng接入,Spring会提供一个{@link ValidationAutoConfiguration#methodValidationPostProcessor}后置处理器
 *      给类中添加了@Validated注解的类生成代理对象,并且在方法执行的过程中进行拦截{@link ValidationAutoConfiguration.MethodValidationInterceptor},对参数进行校验
 *      该代理对象还可以对返回值进行校验
 *
 *      在复杂对象参数解析的过程中,会自动对对象字段进行校验,校验出来的结果之后,如果对象参数后面紧挨的下一个参数为Error(BindingResult)类型,则不会抛出异常,则会把校验信息存入Error(BindingResult)中
 *      这是在springmvc源码中体现的
 *
 *      2. 在SpringMVC中,对于非复杂对象的类型参数,是不会进行参数校验的,即使添加了@Validated或者@Valid注解,因为这这些简单类型不支持Bean Validation规范
 *      但是,会对复杂的对象(JavaBean)进行参数校验,前提是参数中标记了@Validated或者@Valid注解,SpringMVC会自动对对象进行参数校验
 *      在复杂对象(JavaBean)参数解析的过程中,校验出来的结果之后,如果对象参数后面紧挨的下一个参数为Error(BindingResult)类型,如果校验失败也不会抛出异常,而是把校验信息存入Error(BindingResult)中
 *      默认不会给标注了@Validated的类生成代理对象,因为不会有自动配置,如果要完成简单参数的校验,我们有两种做法
 *         1. 使用SpringBoot类似的做法,给容器中注册MethodValidationPostProcessor后置处理器,该处理器会注册一个拦截器,拦截器用于校验参数的validation注解信息,既可以校验参数,也可以校验返回值
 *         2. 可以使用AOP切面,拦截方法参数,使用Validator进行校验
 *
 *          @Bean
 *          public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment,@Lazy Validator validator) {
 *              // 或者直接创建MethodValidationPostProcessor对象
 * 		        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
 * 		        boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
 * 		        processor.setProxyTargetClass(proxyTargetClass);
 * 		        processor.setValidator(validator);
 * 		        return processor;
 *          }
 *
 *     3. 扩展,配置Validator
 *      @Configuration
 *      public class LuckConfig implements WebMvcConfigurer {
 *          // 手动配置Validator
 *          public LocalValidatorFactoryBean localValidatorFactoryBean() {
 *               return new LocalValidatorFactoryBean();
 *          }
 *          // 通过WebMvcConfigurer配置
 *          @Override
 *          public Validator getValidator() {
 *              return new LocalValidatorFactoryBean();
 *          }
 *     }
 *
 * <p>
 *      注意: 非JavaBean字段参数的下个参数为BindingResult的话,会抛出异常,因为只有在JavaBean对象校验的时候,会添加一个"BindingResult.对象名"的标记
 *           因此在参数解析的时候,解析到BindingResult,则会从Model对象中找以"BindingResult."开头的数据,如果没有保存"BindingResult."的标记,则会抛出异常
 *           {@link ErrorsMethodArgumentResolver}
 *           因此,只有在JavaBean参数校验的时候,才能使用BindingResult参数接收错误信息
 * </pre>
 */


// 参数解析器,用于解析参数为Errors(BindingResult参数)
public class ErrorsMethodArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        Class<?> paramType = parameter.getParameterType();
        return Errors.class.isAssignableFrom(paramType);
    }

    @Override
    @Nullable
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        // 获取运行中的Model数据
        ModelMap model = mavContainer.getModel();
        // 获取model中存入的所有数据,是否存在"BindingResult.class.getName() + "."标记
        // 该key是标记该对象是JavaBean对象,进行了校验操作
        String lastKey = CollectionUtils.lastElement(model.keySet());
        if (lastKey != null && lastKey.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
            return model.get(lastKey);
        }
        // 如果不包含"BindingResult.class.getName() + "."标记,则抛出异常
        throw new IllegalStateException(
                "An Errors/BindingResult argument is expected to be declared immediately after " +
                        "the model attribute, the @RequestBody or the @RequestPart arguments " +
                        "to which they apply: " + parameter.getMethod());
    }

}

// 参数解析器,用于解析JavaBean参数,或者标有@ModelAttribute注解的参数
public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
    public boolean supportsParameter(MethodParameter parameter) {
        // 存在@ModelAttribute注解,或者参数指定了不是必须的,并且是JavaBean类型
        return (parameter.hasParameterAnnotation(ModelAttribute.class) || (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
    }

    @Nullable
    public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        // 获取参数名称
        String name = ModelFactory.getNameForParameter(parameter);
        // 获取注解信息
        ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
        if (ann != null) {
            // 设置当前参数是否需要绑定
            mavContainer.setBinding(name, ann.binding());
        }

        // 参数对象
        Object attribute = null;
        // 异常结果对象
        BindingResult bindingResult = null;
        // 如果在Model中存在该参数值
        if (mavContainer.containsAttribute(name)) {
            // 直接从Model中获取数据
            attribute = mavContainer.getModel().get(name);
        } else {
            // 如果Model中没有存入该对象,则创建对象的实例
            try {
                attribute = createAttribute(name, parameter, binderFactory, webRequest);
            } catch (BindException ex) {
                // 创建对象失败,是否需要抛出绑定异常
                // 当参数校验失败,判断当前参数的下一个参数为BindingResult,如果不是,则抛出异常
                if (isBindExceptionRequired(parameter)) {
                    //无BindingResult参数,直接抛出异常
                    throw ex;
                }
                // 保存校验的结果对象
                bindingResult = ex.getBindingResult();
            }
        }
        // 如果创建对象成功
        if (bindingResult == null) {
            // 创建对象属性值绑定器
            WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
            // 如果存在绑定的对象
            if (binder.getTarget() != null) {
                // 如果没有禁用参数绑定
                if (!mavContainer.isBindingDisabled(name)) {
                    // 进行参数绑定,就是将参数从request中获取,进行类型转换,然后设置到对象中,都是由binder对象完成
                    this.bindRequestParameters(binder, webRequest);
                }
                // 参数校验@Validated和@Valid注解
                this.validateIfApplicable(binder, parameter);
                // 校验结果存在binder的bindingResult中,并且校验失败之后,是否必须抛出绑定异常
                if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                    throw new BindException(binder.getBindingResult());
                }
            }
            // 如果参数解析到的参数值和实际类型不一致,进行类型转换
            if (!parameter.getParameterType().isInstance(attribute)) {
                attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
            }
            // 获取数据绑定的结果
            bindingResult = binder.getBindingResult();
        }

        // 获取绑定结果中的数据
        Map<String, Object> bindingResultModel = bindingResult.getModel() {
            // 标记两个字段,一个是对象信息,一个是绑定信息
            Map<String, Object> model = new LinkedHashMap<>(2);
            // 从名称到目标对象的映射。
            model.put(getObjectName(), getTarget());
            // 错误实例,即使没有错误
            model.put(MODEL_KEY_PREFIX + getObjectName(), this);
            return model;
        }
        // 删除之前绑定的数据
        mavContainer.removeAttributes(bindingResultModel);
        // 将新数据重新保存
        mavContainer.addAllAttributes(bindingResultModel);
        // 返回绑定好的参数对象
        return attribute;
    }

    // 校验失败之后,是否必须抛出绑定异常
    protected boolean isBindExceptionRequired(MethodParameter parameter) {
        // 获取参数的位置
        int i = parameter.getParameterIndex();
        // 获取到参数的类型
        Class<?>[] paramTypes = parameter.getExecutable().getParameterTypes();
        // 如果当前参数的下一个参数为Errors(BindingResult),则不会抛出异常,否则抛出异常
        boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
        return !hasBindingResult;
    }

    // 参数校验,是否存在@Valid***注解信息
    protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
        // 获取参数的注解
        for (Annotation ann : parameter.getParameterAnnotations()) {
            // 校验@Valid***注解
            Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann) {
                Class<? extends Annotation> annotationType = ann.annotationType();
                String annotationName = annotationType.getName();
                // 如果注解为@Valid,没有需要校验的分组
                if ("javax.validation.Valid".equals(annotationName)) {
                    return EMPTY_OBJECT_ARRAY;
                }
                // 注解为Validated,获取注解的value值,获取校验的分组信息
                Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
                if (validatedAnn != null) {
                    Object hints = validatedAnn.value();
                    // 如果不为空,返回指定的分组,否则返回EMPTY_OBJECT_ARRAY
                    return this.convertValidationHints(hints);
                }
                // 如果都不是上面两者,如果注解类型为Valid开头的注解,或者对应的值
                if (annotationType.getSimpleName().startsWith("Valid")) {
                    Object hints = AnnotationUtils.getValue(ann);
                    // 如果不为空,返回指定的分组,否则返回EMPTY_OBJECT_ARRAY
                    return convertValidationHints(hints);
                }
                return null;
            }
            // 如果存在@Valid***注解,则不为null
            if (validationHints != null) {
                // 进行参数校验
                binder.validate(validationHints);
                break;
            }
        }
    }

    class WebDataBinder {
        // 进行分组校验,并将结果
        public void validate(Object... validationHints) {
            // 获取需要校验的对象
            Object target = this.getTarget();
            // 从Binder对象中获取BindingResult,如果没有,创建一个保存到当前Binder对象中,用于保存校验结果
            BindingResult bindingResult = this.getBindingResult();
            // 遍历所有的校验器,这个校验器是从@EnableMvc中,注入的RequestMappingHandlerAdapter设置的一个ConfigurableWebBindingInitializer对象
            // 该ConfigurableWebBindingInitializer对象中保存Spring中保存的类型转换器和参数转换器,也是通过@EnableMvc导入的WebMvcConfigurationSupport一起使用@Bean注入的
            // 然后在WebDataBinderFactory创建WebDataBinder对象的时候,指定ConfigurableWebBindingInitializer该对象的initBinder方法,然后将ConfigurableWebBindingInitializer的数据都设置导WebDataBinder对象中
            for (Validator validator : this.getValidators()) {
                // 如果需要进行分组校验
                if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
                    // 调用Validator校验器开始校验
                    /**
                     * {@link LocalValidatorFactoryBean#validate}
                     */
                    ((SmartValidator) validator).validate(target, bindingResult, validationHints);
                }
                // 普通的校验器
                else if (validator != null) {
                    /**
                     * {@link LocalValidatorFactoryBean#validate}
                     */
                    validator.validate(target, bindingResult);
                }
            }
        }
    }
}

public class LocalValidatorFactoryBean extends SpringValidatorAdapter implements SmartValidator, Validator {
    // 代理的Validator
    private Validator targetValidator;

    // 内置的属性名
    static {
        internalAnnotationAttributes.add("message");
        internalAnnotationAttributes.add("groups");
        internalAnnotationAttributes.add("payload");
    }

    // 设置需要代理的Validator
    public void setTargetValidator(Validator targetValidator) {
        this.targetValidator = targetValidator;
    }

    // 初始化操作
    public void afterPropertiesSet() {
        // 获取对Validator工厂的配置对象
        Configuration<?> configuration = Validation.byDefaultProvider().configure();
        ConstraintValidatorFactory targetConstraintValidatorFactory = this.constraintValidatorFactory;
        // 对Validator工厂进行配置
        if (targetConstraintValidatorFactory == null && this.applicationContext != null) {
            targetConstraintValidatorFactory = new SpringConstraintValidatorFactory(this.applicationContext.getAutowireCapableBeanFactory());
        }
        if (targetConstraintValidatorFactory != null) {
            configuration.constraintValidatorFactory(targetConstraintValidatorFactory);
        }
        if (this.parameterNameDiscoverer != null) {
            configureParameterNameProvider(this.parameterNameDiscoverer, configuration);
        }
        // 构建Validator工厂
        this.validatorFactory = configuration.buildValidatorFactory();
        // 创建Validator,并保存
        this.setTargetValidator(((ValidatorFactoryImpl) this.validatorFactory).getValidator());
    }

    // 分组验证整个对象
    @Override
    public void validate(Object target, Errors errors, Object... validationHints) {
        // 如果存在代理的Validator,在上面LocalValidatorFactoryBean创建过,通过ValidatorFactoryImpl创建的
        if (this.targetValidator != null) {
            // 将提供的信息进行过滤,只过滤Class类型指定的分组
            Class<?>[] groups = asValidationGroups(validationHints);
            // 调用Hibernate提供的validator校验
            Set<ConstraintViolation<Object>> violations = this.targetValidator.validate(target, groups);
            // 处理校验结果,将Validator校验的结果保存到Errors(BindingResult)对象中
            processConstraintViolations(violations, errors);
        }
    }

    // 验证对象的某个字段的值
    @Override
    public void validateValue(Class<?> targetType, String fieldName, @Nullable Object value, Errors errors, Object... validationHints) {
        // 和validate方法,这个事调用验证某个字段的api
        if (this.targetValidator != null) {
            // 处理校验结果,将Validator校验的结果保存到Errors(BindingResult)对象中
            processConstraintViolations(this.targetValidator.validateValue((Class) targetType, fieldName, value, asValidationGroups(validationHints)), errors);
        }
    }
}


// 在spring.factories中,配置了org.springframework.context.ApplicationListener = org.springframework.boot.autoconfigure.BackgroundPreinitializer
// 该类是一个监听器,处理ApplicationEnvironmentPreparedEvent事件,环境预处理完成的回调事件
@Order(LoggingApplicationListener.DEFAULT_ORDER + 1)
public class BackgroundPreinitializer implements ApplicationListener<SpringApplicationEvent> {
    public void onApplicationEvent(SpringApplicationEvent event) {
        // 确定是第一次被初始化
        if (event instanceof ApplicationEnvironmentPreparedEvent && preinitializationStarted.compareAndSet(false, true)) {
            // 初始化一些配置信息
            this.performPreinitialization();
        }
    }

    private void performPreinitialization() {
        // 启动新线程,执行下面这些初始化器的run方法
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                // 初始化类型转换器
                runSafely(new ConversionServiceInitializer());
                // 初始化Validator校验器
                runSafely(new ValidationInitializer());
                // 注册各种各样的消息转换器
                runSafely(new MessageConverterInitializer());
                // 注册ObjectMapper
                runSafely(new JacksonInitializer());
                // 初始化字符集
                runSafely(new CharsetInitializer());
            }

            public void runSafely(Runnable runnable) {
                runnable.run();
            }

        }, "background-preinit");
        thread.start();
    }

    class ValidationInitializer implements Runnable {
        // 这里只是为了初始化,触发类加载等等配置,并没有做什么实际性的东西
        @Override
        public void run() {
            // 调用javax.validation.Validation的创建工厂的方法
            // 下面两行代码与 Validation.buildDefaultValidatorFactory()方法完全一样
            Configuration<?> configuration = Validation.byDefaultProvider().configure();
            ValidatorFactory factory = configuration.buildValidatorFactory();
            // 通过工厂获取
            Validator validator = factory.getValidator();
        }
    }


}

// Hibernate实现了javax.validator.ValidatorFactory,class org.hibernate.validator.internal.engine.ValidatorFactoryImpl
public class ValidatorFactoryImpl implements ValidatorFactory {
    @Override
    public Validator getValidator() {
        ConstraintValidatorFactory validatorFactory = constraintCreationContext.getConstraintValidatorManager().getDefaultConstraintValidatorFactory();
        // 创建校验器
        return this.createValidator(validatorFactory, constraintCreationContext, validatorFactoryScopedContext, methodValidationConfiguration);
    }

    // 创建校验器
    public Validator createValidator(ConstraintValidatorFactory constraintValidatorFactory, ConstraintCreationContext constraintCreationContext, ValidatorFactoryScopedContext validatorFactoryScopedContext, MethodValidationConfiguration methodValidationConfiguration) {
        // 获取到校验对象的元数据管理器
        BeanMetaDataManager beanMetaDataManager = beanMetaDataManagers.computeIfAbsent(
                new BeanMetaDataManagerKey(validatorFactoryScopedContext.getParameterNameProvider(),
                        constraintCreationContext.getValueExtractorManager(), methodValidationConfiguration),
                key -> new BeanMetaDataManagerImpl(constraintCreationContext, executableHelper,
                        validatorFactoryScopedContext.getParameterNameProvider(), javaBeanHelper,
                        beanMetadataClassNormalizer, validationOrderGenerator, buildMetaDataProviders(),
                        methodValidationConfiguration)

        );
        // 返回Validator的实现类ValidatorImpl
        return new ValidatorImpl(constraintValidatorFactory, beanMetaDataManager, constraintCreationContext.getValueExtractorManager(), constraintCreationContext.getConstraintValidatorManager(), validationOrderGenerator, validatorFactoryScopedContext);
    }
}

class ValidatorImpl implements Validator, ExecutableValidator {

}

// 在springboot的自动配置中
@AutoConfiguration
@ConditionalOnClass(ExecutableValidator.class)
@ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider")
@Import(PrimaryDefaultValidatorPostProcessor.class)
public class ValidationAutoConfiguration {

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    @ConditionalOnMissingBean(Validator.class)
    public static LocalValidatorFactoryBean defaultValidator(ApplicationContext applicationContext) {
        // 会注册一个LocalValidatorFactoryBean,它本身是一个Validator
        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
        return factoryBean;
    }

    @Bean
    @ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
    public static FilteredMethodValidationPostProcessor methodValidationPostProcessor(Environment environment, @Lazy Validator validator, ObjectProvider<MethodValidationExcludeFilter> excludeFilters) {
        // 拦截@Validated方法的后置处理器
        FilteredMethodValidationPostProcessor processor = new FilteredMethodValidationPostProcessor(excludeFilters.orderedStream());
        // 获取生成代理对象的类型,CGLIB还是JDK
        boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
        processor.setProxyTargetClass(proxyTargetClass);
        // 设置校验器
        processor.setValidator(validator);
        return processor;
    }


    public static class FilteredMethodValidationPostProcessor extends MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor {
        // 校验的注解类型
        private Class<? extends Annotation> validatedAnnotationType = Validated.class;
        // 校验器
        private Validator validator;
        // 要生成代理对象的拦截器
        protected Advisor advisor;

        @Override
        public void afterPropertiesSet() {
            // 创建一个切面,因为存在Validated注解的类或者方法,需要生成代理对象
            Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
            // 创建一个通知,拦截器
            Advice advice = createMethodValidationAdvice(this.validator);
            this.advisor = new DefaultPointcutAdvisor(pointcut, advice);
        }

        protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
            return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
        }


        // 预处理代理工厂
        @Override
        protected ProxyFactory prepareProxyFactory(Object bean, String beanName) {
            if (this.beanFactory != null) {
                // 暴露目标对象,将目标对象按照指定的key保存到BeanDefinition中
                AutoProxyUtils.exposeTargetClass(this.beanFactory, beanName, bean.getClass());
            }
            // 创建代理工厂
            ProxyFactory proxyFactory = super.prepareProxyFactory(bean, beanName) {
                ProxyFactory proxyFactory = new ProxyFactory();
                proxyFactory.copyFrom(this);
                // 设置需要代理的对象
                proxyFactory.setTarget(bean);
                return proxyFactory;
            }
            // 是否使用CBlib代理
            if (!proxyFactory.isProxyTargetClass() && this.beanFactory != null && AutoProxyUtils.shouldProxyTargetClass(this.beanFactory, beanName)) {
                proxyFactory.setProxyTargetClass(true);
            }
            return proxyFactory;
        }

        // 当前类MethodValidationPostProcessor就是为了
        public Object postProcessAfterInitialization(Object bean, String beanName) {
            // 如果当前没有拦截器,或者该bean本身是一个特殊的接口AopInfrastructureBean(标记该类为AOP的Bean)
            if (this.advisor == null || bean instanceof AopInfrastructureBean) {
                return bean;
            }
            // 该Bean是一个代理工厂,创建代理对象的工厂类
            if (bean instanceof Advised) {
                Advised advised = (Advised) bean;
                if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
                    // 将我们的当前类需要应用的切面添加到现有代理的Advisor链中
                    if (this.beforeExistingAdvisors) {
                        advised.addAdvisor(0, this.advisor);
                    } else {
                        advised.addAdvisor(this.advisor);
                    }
                    return bean;
                }
            }
            // 该bean是否可以应用上当前类需要应用的切面
            if (this.isEligible(bean, beanName)) {
                // 当前切面可以作用于该Bean对象,创建代理对象
                ProxyFactory proxyFactory = this.prepareProxyFactory(bean, beanName);
                // 没有设置使用CGLIB
                if (!proxyFactory.isProxyTargetClass()) {
                    // 计算需要代理的接口
                    this.evaluateProxyInterfaces(bean.getClass(), proxyFactory);
                }
                // 添加切面
                proxyFactory.addAdvisor(this.advisor);
                // 自定义代理工厂
                this.customizeProxyFactory(proxyFactory);
                // 创建代理对象
                return proxyFactory.getProxy(classLoader);
            }
            // 不需要代理直接返回
            return bean;
        }

    }

    public static class MethodValidationInterceptor implements MethodInterceptor {
        // 校验器
        private final Validator validator;

        public Object invoke(MethodInvocation invocation) throws Throwable {
            // 避免在FactoryBean.getObjectType/isSingleton上调用验证器
            if (this.isFactoryBeanMetadataMethod(invocation.getMethod())) {
                return invocation.proceed();
            }
            // 确定校验对象需要校验的分组
            Class<?>[] groups = this.determineValidationGroups(invocation);

            // 标准Bean验证1.1 API
            // Validator本身只提供对象或者对象属性的校验,对于校验方法,它并没有提供API
            // 而是提供了一个ExecutableValidator对于方法的校验器,该对象可以校验方法参数,方法返回值,构造参数
            ExecutableValidator execVal = this.validator.forExecutables();
            // 获取到需要校验的方法
            Method methodToValidate = invocation.getMethod();
            // 校验的结果
            Set<ConstraintViolation<Object>> result;
            // 校验的对象
            Object target = invocation.getThis();

            // 校验方法参数
            result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups);
            // 如果参数校验不同过,抛出异常
            if (!result.isEmpty()) {
                throw new ConstraintViolationException(result);
            }
            // 执行目标方法
            Object returnValue = invocation.proceed();
            // 再校验返回值
            result = execVal.validateReturnValue(target, methodToValidate, returnValue, groups);
            // 如果返回值校验失败,抛出异常
            if (!result.isEmpty()) {
                throw new ConstraintViolationException(result);
            }
            return returnValue;
        }

        // 确定校验对象需要校验的分组
        protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
            // 找方法中的Validated注解
            Validated validatedAnn = AnnotationUtils.findAnnotation(invocation.getMethod(), Validated.class);
            // 如果方法中不存在
            if (validatedAnn == null) {
                // 找类中的注解
                Object target = invocation.getThis();
                validatedAnn = AnnotationUtils.findAnnotation(target.getClass(), Validated.class);
            }
            // 返回注解中的分组的值
            return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]);
        }
    }
}

SpringBoot实战:如何优雅处理@Valid校验失效引发的MethodArgumentNotValidException 本文深入探讨了SpringBoot中@Valid参数校验失效时引发的MethodArgumentNotValidException异常。通过分析异常根源,提供了从基础到高级的全局异常处理方案,包括精准捕获异常、构建结构化错误响应、处理嵌套校验等实战技巧,并分享了分组校验、自定义注解等最佳实践,帮助开发者构建健壮优雅的API参数校验体系。 阅读详情

相关推荐

基于SpringBoot的校园快递管理系统设计与实现

SpringBoot作为Java企业级开发的主流框架,通过自动配置与起步依赖机制,大幅简化了传统SSM项目的搭建复杂度,成为快速构建Web应用的首选技术栈。其内嵌容器与约定优于配置的理念,让开发者能够更专注于业务逻辑实现。结合MyBatisPlus实现数据层的高效CRUD,利用Redis处理取件码缓存与防重复提交,配合前后端分离架构,可构建出体验流畅的管理系统。在校园快递场景中,快递积压、取件混乱是长期痛点,一套覆盖入库、出库、取件验证与统计分析的线上管理平台,既能提升驿站运营效率,也能作为毕业设计或Jav

weixin_29597551的博客 306

Java for Web学习笔记(七七):Validation(1)启动验证

Bean validation和Hibernate Validator 我们经常要验证数据的输入和输出是否合规,例如不允许为null,不允许为空,对于email地址,其正则表达式如下: /* 正则表达式: * ^[a-z0-9`!#$%^&*'{}?/+=|_~-] : ^ 匹配输入字符串的开始位置 []表示匹配内部的任何一个 * + 表示前面的表达式出现1次或者多次 * (\\.[a-

愷风(Wei)的专栏 7242

UE4 GamePlay框架深度解析:从蓝图到C++构建多人游戏核心

在游戏开发与实时交互应用领域,一套清晰、可扩展的底层框架是项目成功的关键。GamePlay框架作为虚幻引擎(UE4/UE5)的核心架构,定义了游戏世界中对象交互、状态管理与网络同步的基本原理。它通过职责分离的设计模式,将游戏规则(GameMode)、全局状态(GameState)、玩家状态(PlayerState)、可操控实体(Pawn/Character)及其控制器(Controller)等核心类有机组织起来,确保了逻辑的清晰性与多人游戏数据的一致性。其技术价值在于为开发者提供了一套经过验证的最佳实践,大

weixin_33924770的博客 609

No target ValidatorFactory set (through reference chain: org.springframework.validation.beanvalidati

在执行json序列化对象时,抛出的问题,经过代码分析,发现是自定义的日志拦截器里做了序列化引起,那么这个拦截器拦截的对象有哪些呢?发现是加了指定注解的地方,我自定义了一个注解写在controller类里,但由于在类里面,定义了一个普通bean,它被当做普通controller方法进行拦截,但这个bean是不能被序列化的。把bean单独提取出去放在configuration里就好。

qq_30434271的博客 639

解决uc浏览器劫持video标签问题

解决uc视频悬浮播放 ,video标签被浏览器劫持 ,vide0适配

smile_y_y的博客 3447

零基础用AHK操作Excel:从数据读取到自动填写(附完整脚本解析)

本文为零基础用户详细介绍了如何使用AutoHotkey(AHK)通过COM接口高效操作Excel,实现从数据自动读取到填写的完整自动化流程。文章包含完整的脚本解析、环境搭建指南、核心代码逐行讲解以及实际应用案例,帮助办公人员大幅提升数据处理效率,告别重复劳动。

weixin_29202213的博客 552

SpringMVC介绍之Validation

SpringMVC介绍之Validation        对于任何一个应用而言在客户端做的数据有效性验证都不是安全有效的,这时候就要求我们在开发的时候在服务端也对数据的有效性进行验证。SpringMVC自身对数据在服务端的校验有一个比较好的支持,它能将我们提交到服务端的数据按照我们事先的约定进行数据有效性验证,对于不合格的数据信息SpringMVC会把它保存在错误对象中,这些错误信息我们也...

Elim的博客 936

ADS1220 STM32硬件SPI驱动程序

前一段时间温故了下标准库,使用的是F1系列芯片,测试程序。

随风飘零翼的博客 5762

【第13章】spring-mvc之validator

【第20章】spring-validator虽然前面已经在spring介绍过,但是为了保证代码可用,还是会从头讲到尾,尽量把关键点列出来讲给大家。回到顶部使用起来比spring要简单些,希望能帮助到大家。

zjg 807

SAP 财务接口开发:完全清账 vs 标准过账的 CO-PA(获利能力字段) 赋值全解析(含设计哲学)

用于完全清账场景:用于标准凭证过账/部分清账两者对 CO-PA 数据的传递方式截然不同。本文不仅提供完整的代码实现,更深入解析为什么 SAP 要这样设计——了解设计哲学,才能灵活应对各类需求。完全清账→ 调用,必须在T_FTPOST中传递,其他特征用BSEG-RKE_前缀。标准过账/部分清账→ 调用,在CRITERIA表中传递特征,字段名直接用WERKSARTNR等。SAP 的设计智慧面向不同场景,采用不同机制,各司其职。通过接口抽象,将复杂的 CO-PA 配置逻辑封装在内部,对外提供简洁的调用方式。

Pegasus666的博客 326

2025海外代理IP深度横评:Bright Data、IPFoxy、SmartProxy等六大服务商实战对比

本文深度横评2025年六大海外代理IP服务商(Bright Data、IPFoxy、SmartProxy、IPIPGO、Kookeey及青果网络),基于实战测试数据,从IP池质量、速度稳定性、性价比等维度进行对比。针对跨境电商、数据爬虫、社交媒体管理等不同业务场景,提供精准的选购指南与避坑建议,帮助企业用户根据自身需求选择最合适的代理IP解决方案。

weixin_29196891的博客 431

【SpringMVC应用篇】Spring Validation 参数校验

Spring Validation1. Spring ValidationJSR-303 简介2. JSR-303 基本的校验规则2.1空检查2.2 布尔检查2.3 长度检查2.4 日期检查2.5 正则检查2.6 数值检查2.7 对象校验3 使用 Spring Validation 验证POM实体类参数校验UsercontrollerservicePostMan测试响应数据全局处理异常表单传参校验单个参数校验 1. Spring Validation JSR-303 简介 JSR-303 是 JavaEE

输入技术、输出想法 2771

基于ESP32和OV5640的目标检测系统

本文详细介绍了基于ESP32-S3和OV5640摄像头构建目标检测系统的完整方案。系统采用YOLOv8-Nano轻量级模型,通过INT8量化实现边缘部署,在UXGA分辨率下达到25fps帧率。文章从硬件选型、图像采集优化、模型量化部署、实时推理流水线构建等维度展开,重点解决了内存管理、电源稳定性和异常处理等嵌入式系统关键问题。该方案在资源受限条件下实现了150ms以内的端到端延迟,为物联网视觉应用提供了低成本、低功耗的解决方案,适用于智能家居、工业检测等多个场景。

reset2021的博客 327

SpringBoot教程30整合SpringMVC之WebDataBinder处理请求参数绑定(二)

在上一章节,SpringBoot2.x系列教程29--整合SpringMVC之@InitBinder处理请求参数的绑定(上)的内容中,我给大家讲解了@InitBinder的原理及使用教程,本章节我继续讲解SpringMVC中的参数传递及数据绑定。 本章节主要是讲解WebDataBinder的内容。 一. WebDataBinder简介 从上面的代码案例中,我们可以看到另一个类,那就是WebDataBinder! 1. WebDataBinder的由来 在servlet中,有一个方法:request

qfchenjunbo的博客 813

无迹卡尔曼滤波 (UKF, Unscented Kalman Filter) 算法详解及案例分析

无迹卡尔曼滤波是一种用于估计非线性系统状态的递归算法。其目标是通过结合系统模型和观测数据,估计系统的最优状态。

闲人编程的博客 2622

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

class<?> clazz参数_SpringBoot教程30整合SpringMVC之WebDataBinder处理请求参数绑定(二)

SpringBoot2.x系列教程30--整合SpringMVC之WebDataBinder处理请求参数的绑定(二)作者:一一哥在上一章节,SpringBoot2.x系列教程29--整合SpringMVC之@InitBinder处理请求参数的绑定(上)的内容中,我给大家讲解了@InitBinder的原理及使用教程,本章节我继续讲解SpringMVC中的参数传递及数据绑定。本章节主要是讲解WebDa...

weixin_39763033的博客 382

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

springboot处理参数再转发请求_SpringBoot教程30整合SpringMVC之WebDataBinder处理请求参数绑定(二)...

SpringBoot2.x系列教程30--整合SpringMVC之WebDataBinder处理请求参数的绑定(二)作者:一一哥在上一章节,SpringBoot2.x系列教程29--整合SpringMVC之@InitBinder处理请求参数的绑定(上)的内容中,我给大家讲解了@InitBinder的原理及使用教程,本章节我继续讲解SpringMVC中的参数传递及数据绑定。本章节主要是讲解WebDa...

weixin_39787826的博客 96

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

boot spring 对参数检测_SpringBoot教程30整合SpringMVC之WebDataBinder处理请求参数绑定(二)...

SpringBoot2.x系列教程30--整合SpringMVC之WebDataBinder处理请求参数的绑定(二)作者:一一哥在上一章节,SpringBoot2.x系列教程29--整合SpringMVC之@InitBinder处理请求参数的绑定(上)的内容中,我给大家讲解了@InitBinder的原理及使用教程,本章节我继续讲解SpringMVC中的参数传递及数据绑定。本章节主要是讲解WebDa...

weixin_39926588的博客 94

SpringBoot学习5.7-SpringMVC-参数验证-自定义参数验证器

目录 1.自定义参数验证器说明 2.定义pojo 3.自定义参数验证器 4.绑定自定义参数验证器 5.控制器方法 6.html/js 7.测试结果 1.自定义参数验证器说明 springmvc参数转换后获得一个JavaPojo,然后进入参数验证阶段。流程参考SpringBoot学习5.1-SpringMVC流程。 springmvc支持自定义参数验证器及校验策略。 使用要点:...

echo的博客 518

SpringMVC

SpringMVC

2404_88294688的博客 1836

Springboot官网学习(7、Web应用程序 十二Spring Web MVC 之ConfigurableWebBindingInitializer 初始化参数绑定配置】)

这节来说springboot中的初始化参数配置,使用过SSM的都知道,springmvc中的初始化参数配置是可以通过@InitBinder来实现参数初始化配置, 示例:以去除字符串前后空格为例: package com.osy.config; import com.osy.editorregistrar.ZyDateEditor; import org.springframework.beans.propertyeditors.StringTrimmerEditor; import org.spring

CoderOu 574

SpringBoot 自定义ConfigurableWebBindingInitializer(可配置web初始化绑定器)

SpringBoot中自定义ConfigurableWebBindingInitializer的原因之一有如下: 1、可自定义属性编辑器(PropertyEditor) 一、SpringBoot为什么可以将我们自定义的ConfigurableWebBindingInitializer自动装配到SpringMVC中 //WebMvcAutoConfiguration中有如下代码表示如果spring容...

Michean的博客 2302

Springboot中hibernate参数校验,自定义校验注解

使用hibernate的校验框架进行参数校验,并自定义校验注解和逻辑 hibernate校验示例使用校验注解配合@Valid注解绑定SpringMvc的参数进行参数校验pojo类上加校验注解import org.hibernate.validator.constraints.*; import javax.validation.constraints.*; import java.util.Da...

Laputa_Spring 712

SpringBoot与SSM框架构建学校招生管理系统实践

Java企业级开发中,SpringBoot与SSM(Spring+SpringMVC+MyBatis)框架组合是构建Web应用的高效方案。SpringBoot通过自动配置和起步依赖简化了项目搭建,内嵌Tomcat服务器便于部署;SSM框架则提供了完善的MVC架构和灵活的数据库访问能力。这种技术组合特别适合教育行业的数字化需求,如学校招生管理系统这类需要处理复杂业务流程的应用。系统实现学生信息管理、在线报名、资料审核等核心功能时,利用MyBatis处理复杂SQL查询,Spring事务管理确保数据一致性。通过R

weixin_30326515的博客 497

Java:HTTP请求全链路全景深度解析/浏览器→网关→Controller→Service→Mapper→数据库/逐行代码串讲/理解分层调用方式

一次普通的前端接口请求,看似简单,实则经历了6大核心层级、数十项底层校验与逻辑处理。从浏览器的HTTP协议封装,到网关的流量安全管控,再到业务层的逻辑与事务、数据层的SQL执行、数据库的索引与锁机制,每一层都有独立的职责、规则与故障风险。 掌握该完整链路,可实现快速定位线上BUG、优化接口性能、规范代码开发、理解微服务底层架构,是后端开发、面试复盘、性能调优的核心基础能力。

snow的博客 540
上一篇: SpringMVC中,/和/*和/**分别表示什么
下一篇: SpringBoot-SpringMVC中的ContentNegotiationStrategy内容协商策略的作用和原理
安缇吖
博客等级 码龄9年 411粉丝 135原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值