From 9ade52dbe25bdcf5751590c98bacfe7053f5dddf Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 28 Nov 2023 16:06:07 +0000 Subject: [PATCH] Exclude Part and MultipartFile from nested constructor binding Closes gh-31669 --- .../org/springframework/validation/DataBinder.java | 11 +++++++++-- .../web/bind/ServletRequestDataBinder.java | 8 ++++++++ .../web/bind/support/WebRequestDataBinder.java | 9 +++++++++ .../ModelAttributeMethodProcessorTests.java | 8 +++++++- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/validation/DataBinder.java b/spring-context/src/main/java/org/springframework/validation/DataBinder.java index 14df019fb0be..9c70ada6c5dd 100644 --- a/spring-context/src/main/java/org/springframework/validation/DataBinder.java +++ b/spring-context/src/main/java/org/springframework/validation/DataBinder.java @@ -948,7 +948,7 @@ private Object createObject(ResolvableType objectType, String nestedPath, ValueR Class paramType = paramTypes[i]; Object value = valueResolver.resolveValue(paramPath, paramType); - if (value == null && shouldCreateObject(param)) { + if (value == null && shouldConstructArgument(param)) { ResolvableType type = ResolvableType.forMethodParameter(param); args[i] = createObject(type, paramPath + ".", valueResolver); } @@ -1008,7 +1008,14 @@ private Object createObject(ResolvableType objectType, String nestedPath, ValueR return (isOptional && !nestedPath.isEmpty() ? Optional.ofNullable(result) : result); } - private static boolean shouldCreateObject(MethodParameter param) { + /** + * Whether to instantiate the constructor argument of the given type, + * matching its own constructor arguments to bind values. + *

By default, simple value types, maps, collections, and arrays are + * excluded from nested constructor binding initialization. + * @since 6.1.2 + */ + protected boolean shouldConstructArgument(MethodParameter param) { Class type = param.nestedIfOptional().getNestedParameterType(); return !(BeanUtils.isSimpleValueType(type) || Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type) || type.isArray()); diff --git a/spring-web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java b/spring-web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java index f0b361b09e2f..921cf2e503f6 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java +++ b/spring-web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java @@ -24,6 +24,7 @@ import jakarta.servlet.http.Part; import org.springframework.beans.MutablePropertyValues; +import org.springframework.core.MethodParameter; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.lang.Nullable; @@ -120,6 +121,13 @@ protected ServletRequestValueResolver createValueResolver(ServletRequest request return new ServletRequestValueResolver(request, this); } + @Override + protected boolean shouldConstructArgument(MethodParameter param) { + Class type = param.nestedIfOptional().getNestedParameterType(); + return (super.shouldConstructArgument(param) && + !MultipartFile.class.isAssignableFrom(type) && !Part.class.isAssignableFrom(type)); + } + /** * Bind the parameters of the given request to this binder's target, * also binding multipart files in case of a multipart request. diff --git a/spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java b/spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java index ebf8db439e61..1982222cadb1 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java +++ b/spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java @@ -18,8 +18,10 @@ import jakarta.servlet.ServletRequest; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.Part; import org.springframework.beans.MutablePropertyValues; +import org.springframework.core.MethodParameter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; @@ -30,6 +32,7 @@ import org.springframework.web.bind.WebDataBinder; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.WebRequest; +import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartRequest; import org.springframework.web.multipart.support.StandardServletPartUtils; @@ -118,6 +121,12 @@ public void construct(WebRequest request) { } } + @Override + protected boolean shouldConstructArgument(MethodParameter param) { + Class type = param.nestedIfOptional().getNestedParameterType(); + return (super.shouldConstructArgument(param) && + !MultipartFile.class.isAssignableFrom(type) && !Part.class.isAssignableFrom(type)); + } /** * Bind the parameters of the given request to this binder's target, diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessorTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessorTests.java index 826d768463e2..9335194e8e4f 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessorTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessorTests.java @@ -29,6 +29,7 @@ import org.springframework.core.ResolvableType; import org.springframework.core.annotation.SynthesizingMethodParameter; import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.lang.Nullable; import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -41,6 +42,7 @@ import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.WebRequest; import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.multipart.MultipartFile; import org.springframework.web.testfixture.servlet.MockHttpServletRequest; import static java.lang.annotation.ElementType.CONSTRUCTOR; @@ -298,6 +300,7 @@ public void resolveConstructorListArgumentFromCommaSeparatedRequestParameter() t Object resolved = this.processor.resolveArgument(this.beanWithConstructorArgs, this.container, requestWithParam, factory); assertThat(resolved).isInstanceOf(TestBeanWithConstructorArgs.class); assertThat(((TestBeanWithConstructorArgs) resolved).listOfStrings).containsExactly("1", "2"); + assertThat(((TestBeanWithConstructorArgs) resolved).file).isNull(); } private void testGetAttributeFromModel(String expectedAttrName, MethodParameter param) throws Exception { @@ -375,8 +378,11 @@ static class TestBeanWithConstructorArgs { final List listOfStrings; - public TestBeanWithConstructorArgs(List listOfStrings) { + final MultipartFile file; + + public TestBeanWithConstructorArgs(List listOfStrings, @Nullable MultipartFile file) { this.listOfStrings = listOfStrings; + this.file = file; } }