一次@Validated注解失效问题排查及解决

排查思路

(1)找一个能正确校验的项目接口,通过编写错误的参数,来使得系统抛出异常,然后从异常处开始查看堆栈信息,分析关键变量的赋值
(2)如果找到影响的某个关键变量,然后我们转而关注该变量的赋值部分,再次debug,查找是由谁进行赋值的
(3)直到分析出,某个位置的赋值是有问题的,那么则分析赋值的变量值来源,这样大部分都能找到问题所在

tips:如果没有找到一个正确校验的项目接口,那么我这里给一个入口:org.springframework.web.method.annotation.ModelAttributeMethodProcessor#resolveArgument

排查步骤

org.springframework.web.method.annotation.ModelAttributeMethodProcessor#resolveArgument

@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.bindRequestParameters(binder, webRequest);
			}

			this.validateIfApplicable(binder, parameter);
			if (binder.getBindingResult().hasErrors() && this.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();
	mavContainer.removeAttributes(bindingResultModel);
	mavContainer.addAllAttributes(bindingResultModel);
	return attribute;
}

正常情况下,如果参数校验失败,是会走到
throw new BindException(binder.getBindingResult());这步的,所以要分析binder,所以这个方法this.validateIfApplicable(binder, parameter);执行很重要

validateIfApplicable(binder, parameter)

protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
	Annotation[] var3 = parameter.getParameterAnnotations();
	int var4 = var3.length;

	for(int var5 = 0; var5 < var4; ++var5) {
		Annotation ann = var3[var5];
		Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
		if (validationHints != null) {
			binder.validate(validationHints);
			break;
		}
	}

}

这里找参数上的注解,这里是@Validated,然后执行binder.validate方法,这里的binder为
org.springframework.web.servlet.mvc.method.annotation.ExtendedServletRequestDataBinder,实际执行的是父类org.springframework.validation.DataBinder#validate()

org.springframework.validation.DataBinder#validate()

public void validate() {
	Object target = this.getTarget();
	Assert.state(target != null, "No target to validate");
	BindingResult bindingResult = this.getBindingResult();
	Iterator var3 = this.getValidators().iterator();

	while(var3.hasNext()) {
		Validator validator = (Validator)var3.next();
		validator.validate(target, bindingResult);
	}

}

在这里发现this.getValidators()为空列表,说明validators的赋值有问题(这里就是所谓的关键位置的赋值),接着我们对DataBinder的validators的赋值方法打断点。发现赋值操作由org.springframework.web.bind.support.ConfigurableWebBindingInitializer#initBinder调用

org.springframework.web.bind.support.ConfigurableWebBindingInitializer#initBinder

public void initBinder(WebDataBinder binder) {
	...

	if (this.validator != null && binder.getTarget() != null && this.validator.supports(binder.getTarget().getClass())) {
		binder.setValidator(this.validator);
	}

	...
}

接着我们又需要关注ConfigurableWebBindingInitializer的validator的赋值,发现是由org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration#getConfigurableWebBindingInitializer调用

protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer(FormattingConversionService mvcConversionService, Validator mvcValidator) {
	ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
	initializer.setConversionService(mvcConversionService);
	initializer.setValidator(mvcValidator);
	MessageCodesResolver messageCodesResolver = this.getMessageCodesResolver();
	if (messageCodesResolver != null) {
		initializer.setMessageCodesResolver(messageCodesResolver);
	}

	return initializer;
}

到这,mvcValidator也有值,各方面看起来都很正常,但是为啥org.springframework.validation.DataBinder#validate()中的validators为空,我们回到创建DataBinder那,即:org.springframework.web.method.annotation.ModelAttributeMethodProcessor#resolveArgument方法中的WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
这里我们定位到是:org.springframework.web.bind.support.DefaultDataBinderFactory#createBinder

org.springframework.web.bind.support.DefaultDataBinderFactory#createBinder

public final WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {
	WebDataBinder dataBinder = this.createBinderInstance(target, objectName, webRequest);
	if (this.initializer != null) {
		this.initializer.initBinder(dataBinder, webRequest);
	}

	this.initBinder(dataBinder, webRequest);
	return dataBinder;
}

这里发现,this.initializer不是上述的new 的ConfigurableWebBindingInitializer,而是自定义的com.xxx.config.WebBindingInitializerConfig.MyWebBindingInitializer

/**
 * 配置请求集合上限数量
 */
public static class MyWebBindingInitializer extends ConfigurableWebBindingInitializer {

	@Override
	public void initBinder(WebDataBinder binder) {
		super.initBinder(binder);
		binder.setAutoGrowNestedPaths(true);

		// 配置集合上限数量
		binder.setAutoGrowCollectionLimit(10000);
	}
}

自定义的ConfigurableWebBindingInitializer,那么这个自定义的ConfigurableWebBindingInitializer又是从何处赋值进去的呢,这时候需要关注DefaultDataBinderFactory中的initializer,发现取的是org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getWebBindingInitializer方法返回的,那么这个返回的值又是从哪里赋值的呢,发现还是从自定义的类中赋值的

@Autowired
public WebBindingInitializerConfig(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
	requestMappingHandlerAdapter.setWebBindingInitializer(new MyWebBindingInitializer());
}

真相了,是自定义的ConfigurableWebBindingInitializer 覆盖了RequestMappingHandlerAdapter 默认的,但是自定义的这个并没有设置validator

解决方案

@Configuration
public class WebBindingInitializerConfig {

    @Bean
    public ConfigurableWebBindingInitializer configurableWebBindingInitializer(FormattingConversionService mvcConversionService,
                                                                               Validator mvcValidator) {
        ConfigurableWebBindingInitializer initializer = new MyWebBindingInitializer();
        initializer.setConversionService(mvcConversionService);
        initializer.setValidator(mvcValidator);

        return initializer;
    }

    /**
     * 配置请求集合上限数量
     */
    public static class MyWebBindingInitializer extends ConfigurableWebBindingInitializer {

        @Override
        public void initBinder(WebDataBinder binder) {
            super.initBinder(binder);
            binder.setAutoGrowNestedPaths(true);

            // 配置集合上限数量
            binder.setAutoGrowCollectionLimit(10000);
        }
    }

}

自定义的ConfigurableWebBindingInitializer 设置validator,自定义ConfigurableWebBindingInitializer 注入到Spring容器中,这样走的是org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration#getConfigurableWebBindingInitializer中的从SpringIOC获取到自定义的bean,而不是走的异常处理部分

protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer(FormattingConversionService mvcConversionService, Validator mvcValidator) {
	try {
		return (ConfigurableWebBindingInitializer)this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);
	} catch (NoSuchBeanDefinitionException var4) {
		return super.getConfigurableWebBindingInitializer(mvcConversionService, mvcValidator);
	}
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
@Validated注解失效的原因可能有多种情况。其中一种可能是因为在使用@Validated注解时没有引入必需的包。在使用@Validated注解时,需要引入以下几个包:hibernate-validator-6.0.14.Final.jar、jboss-logging-3.3.2.Final.jar、validation-api-2.0.1.Final.jar、classmate-1.4.0.jar。如果缺少其中一个包,@Validated注解可能会失效。 另外,@Validated注解必须在Controller中需要验证的实体类前面使用。例如,在@PostMapping("/add")方法中,如果没有在SysDept类前面使用@Validated注解,那么@Validated注解也会失效。确保在需要验证的实体类前使用@Validated注解可以确保注解生效。 除了上述两种情况外,@Validated注解还可能失效的原因还有其他,例如配置文件中的校验开关未开启、校验规则未正确配置等。如果以上方法仍然无法解决问题,建议进行更详细的排查,检查配置文件和代码实现是否符合校验要求。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [@Validated注解不生效问题、如何使用](https://blog.csdn.net/liufang_king/article/details/114278404)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [@Validated注解不生效问题汇总大全](https://blog.csdn.net/qiuxuezhe_fei/article/details/128197714)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

来了就走下去

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值