排查思路
(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);
}
}