From a2b616647b4d30e32056b083ec734e0b5809af54 Mon Sep 17 00:00:00 2001 From: Chris Bono Date: Thu, 24 Apr 2025 10:49:46 -0500 Subject: [PATCH 1/2] Avoid matching multipart parameters annotated with @ModelAttribute The ProxyHandlerMethodArgumentResolver now avoids matching multipart parameters annotated with @ModelAttribute. This allows multipart parameters to be handled by RequestParamMethodArgumentResolver which properly handles multipart arguments. Fixes #3258 Related tickets #2937 Signed-off-by: Chris Bono --- .../web/ProxyingHandlerMethodArgumentResolver.java | 8 +++++--- ...oxyingHandlerMethodArgumentResolverUnitTests.java | 12 ++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java b/src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java index 99cbe96dd1..c15b24089d 100644 --- a/src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java @@ -36,11 +36,13 @@ import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.annotation.ModelAttributeMethodProcessor; import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.multipart.support.MultipartResolutionDelegate; /** * {@link HandlerMethodArgumentResolver} to create Proxy instances for interface based controller method parameters. * * @author Oliver Gierke + * @author Chris Bono * @since 1.10 */ public class ProxyingHandlerMethodArgumentResolver extends ModelAttributeMethodProcessor @@ -88,9 +90,9 @@ public boolean supportsParameter(MethodParameter parameter) { return false; } - // Annotated parameter - if (parameter.getParameterAnnotation(ProjectedPayload.class) != null - || parameter.getParameterAnnotation(ModelAttribute.class) != null) { + // Annotated parameter (excluding multipart @ModelAttribute) + if (parameter.hasParameterAnnotation(ProjectedPayload.class) || + (parameter.hasParameterAnnotation(ModelAttribute.class) && !MultipartResolutionDelegate.isMultipartArgument(parameter))) { return true; } diff --git a/src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java index 85f14ea2f3..c7a1bdc274 100755 --- a/src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java +++ b/src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java @@ -27,11 +27,13 @@ import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.util.ReflectionUtils; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.multipart.MultipartFile; /** * Unit tests for {@link ProxyingHandlerMethodArgumentResolver}. * * @author Oliver Gierke + * @author Chris Bono * @soundtrack Karlijn Langendijk & Sönke Meinen - Englishman In New York (Sting, * https://www.youtube.com/watch?v=O7LZsqrnaaA) */ @@ -88,6 +90,14 @@ void doesSupportAtModelAttribute() throws Exception { assertThat(resolver.supportsParameter(parameter)).isTrue(); } + @Test // GH-3258 + void doesNotSupportAtModelAttributeForMultipartParam() throws Exception { + + var parameter = getParameter("withModelAttributeMultipart", MultipartFile.class); + + assertThat(resolver.supportsParameter(parameter)).isFalse(); + } + private static MethodParameter getParameter(String methodName, Class parameterType) { var method = ReflectionUtils.findMethod(Controller.class, methodName, parameterType); @@ -112,5 +122,7 @@ interface Controller { void withForeignAnnotation(@Autowired SampleInterface param); void withModelAttribute(@ModelAttribute SampleInterface param); + + void withModelAttributeMultipart(@ModelAttribute MultipartFile file); } } From 54a85b98b80a78c21cc8a4b72648a795e8d01fc1 Mon Sep 17 00:00:00 2001 From: Chris Bono Date: Thu, 15 May 2025 13:07:19 -0500 Subject: [PATCH 2/2] Allow `@ProjectedPayload` to be used on parameters. The `@ProjectedPayload` annotation can now be used on parameters. This prepares for the upcoming removal of support for non-annotated projections. Fixes #3258 Related tickets #2937 Signed-off-by: Chris Bono --- .../data/web/ProjectedPayload.java | 10 +++++----- ...ProxyingHandlerMethodArgumentResolver.java | 6 +++--- ...andlerMethodArgumentResolverUnitTests.java | 20 +++++++++++++++++++ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/springframework/data/web/ProjectedPayload.java b/src/main/java/org/springframework/data/web/ProjectedPayload.java index 51dbd7d3d5..84c439a139 100644 --- a/src/main/java/org/springframework/data/web/ProjectedPayload.java +++ b/src/main/java/org/springframework/data/web/ProjectedPayload.java @@ -15,11 +15,10 @@ */ package org.springframework.data.web; -import static java.lang.annotation.ElementType.*; -import static java.lang.annotation.RetentionPolicy.*; - import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** @@ -27,11 +26,12 @@ * response payloads to. * * @author Oliver Gierke + * @author Chris Bono * @soundtrack * @since 1.13 */ @Documented -@Retention(RUNTIME) -@Target(TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.PARAMETER }) public @interface ProjectedPayload { } diff --git a/src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java b/src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java index c15b24089d..c47881852c 100644 --- a/src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java @@ -90,9 +90,9 @@ public boolean supportsParameter(MethodParameter parameter) { return false; } - // Annotated parameter (excluding multipart @ModelAttribute) - if (parameter.hasParameterAnnotation(ProjectedPayload.class) || - (parameter.hasParameterAnnotation(ModelAttribute.class) && !MultipartResolutionDelegate.isMultipartArgument(parameter))) { + // Annotated parameter (excluding multipart) + if ((parameter.hasParameterAnnotation(ProjectedPayload.class) || parameter.hasParameterAnnotation( + ModelAttribute.class)) && !MultipartResolutionDelegate.isMultipartArgument(parameter)) { return true; } diff --git a/src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java index c7a1bdc274..8f9988611a 100755 --- a/src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java +++ b/src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java @@ -98,6 +98,22 @@ void doesNotSupportAtModelAttributeForMultipartParam() throws Exception { assertThat(resolver.supportsParameter(parameter)).isFalse(); } + @Test // GH-3258 + void doesSupportAtProjectedPayload() throws Exception { + + var parameter = getParameter("withProjectedPayload", SampleInterface.class); + + assertThat(resolver.supportsParameter(parameter)).isTrue(); + } + + @Test // GH-3258 + void doesNotSupportAtProjectedPayloadForMultipartParam() throws Exception { + + var parameter = getParameter("withProjectedPayloadMultipart", MultipartFile.class); + + assertThat(resolver.supportsParameter(parameter)).isFalse(); + } + private static MethodParameter getParameter(String methodName, Class parameterType) { var method = ReflectionUtils.findMethod(Controller.class, methodName, parameterType); @@ -124,5 +140,9 @@ interface Controller { void withModelAttribute(@ModelAttribute SampleInterface param); void withModelAttributeMultipart(@ModelAttribute MultipartFile file); + + void withProjectedPayload(@ProjectedPayload SampleInterface param); + + void withProjectedPayloadMultipart(@ProjectedPayload MultipartFile file); } }