栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

一次@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 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);
	}
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/946618.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号