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