18
18
import java .lang .annotation .Annotation ;
19
19
import java .util .Arrays ;
20
20
import java .util .List ;
21
+ import java .util .concurrent .ConcurrentHashMap ;
21
22
22
23
import org .springframework .beans .BeansException ;
23
24
import org .springframework .beans .MutablePropertyValues ;
28
29
import org .springframework .core .MethodParameter ;
29
30
import org .springframework .core .annotation .AnnotatedElementUtils ;
30
31
import org .springframework .core .convert .ConversionService ;
32
+ import org .springframework .core .log .LogAccessor ;
31
33
import org .springframework .data .projection .SpelAwareProxyProjectionFactory ;
32
34
import org .springframework .util .ClassUtils ;
33
35
import org .springframework .web .bind .WebDataBinder ;
@@ -52,9 +54,10 @@ public class ProxyingHandlerMethodArgumentResolver extends ModelAttributeMethodP
52
54
53
55
private final SpelAwareProxyProjectionFactory proxyFactory ;
54
56
private final ObjectFactory <ConversionService > conversionService ;
57
+ private final ProjectedPayloadDeprecationLogger deprecationLogger = new ProjectedPayloadDeprecationLogger ();
55
58
56
59
/**
57
- * Creates a new {@link PageableHandlerMethodArgumentResolver } using the given {@link ConversionService}.
60
+ * Creates a new {@link ProxyingHandlerMethodArgumentResolver } using the given {@link ConversionService}.
58
61
*
59
62
* @param conversionService must not be {@literal null}.
60
63
*/
@@ -80,28 +83,36 @@ public void setBeanClassLoader(ClassLoader classLoader) {
80
83
@ Override
81
84
public boolean supportsParameter (MethodParameter parameter ) {
82
85
86
+ // Simple type or not annotated with @ModelAttribute (and flag set to require annotation)
83
87
if (!super .supportsParameter (parameter )) {
84
88
return false ;
85
89
}
86
90
87
91
Class <?> type = parameter .getParameterType ();
88
92
93
+ // Only interfaces can be proxied
89
94
if (!type .isInterface ()) {
90
95
return false ;
91
96
}
92
97
93
- // Annotated parameter (excluding multipart)
94
- if ((parameter .hasParameterAnnotation (ProjectedPayload .class ) || parameter .hasParameterAnnotation (
95
- ModelAttribute .class )) && !MultipartResolutionDelegate .isMultipartArgument (parameter )) {
98
+ // Multipart not currently supported
99
+ if (MultipartResolutionDelegate .isMultipartArgument (parameter )) {
100
+ return false ;
101
+ }
102
+
103
+ // Type or parameter explicitly annotated with @ProjectedPayload
104
+ if (parameter .hasParameterAnnotation (ProjectedPayload .class ) || AnnotatedElementUtils .findMergedAnnotation (type ,
105
+ ProjectedPayload .class ) != null ) {
96
106
return true ;
97
107
}
98
108
99
- // Annotated type
100
- if (AnnotatedElementUtils .findMergedAnnotation (type , ProjectedPayload .class ) != null ) {
109
+ // Parameter annotated with @ModelAttribute
110
+ if (parameter .hasParameterAnnotation (ModelAttribute .class )) {
111
+ this .deprecationLogger .logDeprecationForParameter (parameter );
101
112
return true ;
102
113
}
103
114
104
- // Exclude parameters annotated with Spring annotation
115
+ // Exclude any other parameters annotated with Spring annotation
105
116
if (Arrays .stream (parameter .getParameterAnnotations ())
106
117
.map (Annotation ::annotationType )
107
118
.map (Class ::getPackageName )
@@ -112,8 +123,12 @@ public boolean supportsParameter(MethodParameter parameter) {
112
123
113
124
// Fallback for only user defined interfaces
114
125
String packageName = ClassUtils .getPackageName (type );
126
+ if (IGNORED_PACKAGES .stream ().noneMatch (packageName ::startsWith )) {
127
+ this .deprecationLogger .logDeprecationForParameter (parameter );
128
+ return true ;
129
+ }
115
130
116
- return ! IGNORED_PACKAGES . stream (). anyMatch ( it -> packageName . startsWith ( it )) ;
131
+ return false ;
117
132
}
118
133
119
134
@ Override
@@ -128,4 +143,33 @@ protected Object createAttribute(String attributeName, MethodParameter parameter
128
143
129
144
@ Override
130
145
protected void bindRequestParameters (WebDataBinder binder , NativeWebRequest request ) {}
146
+
147
+ /**
148
+ * Logs a warning message when a parameter is proxied but not explicitly annotated with {@link @ProjectedPayload}.
149
+ * <p>
150
+ * To avoid log spamming, the message is only logged the first time the parameter is encountered.
151
+ */
152
+ static class ProjectedPayloadDeprecationLogger {
153
+
154
+ private static final String MESSAGE = "Parameter %s (%s) is not annotated with @ProjectedPayload - support for parameters not explicitly annotated with @ProjectedPayload (at the parameter or type level) will be dropped in a future version." ;
155
+
156
+ private final LogAccessor logger = new LogAccessor (ProxyingHandlerMethodArgumentResolver .class );
157
+
158
+ private final ConcurrentHashMap <MethodParameter , Boolean > loggedParameters = new ConcurrentHashMap <>();
159
+
160
+ /**
161
+ * Log a warning the first time a non-annotated method parameter is encountered.
162
+ *
163
+ * @param parameter the parameter
164
+ */
165
+ void logDeprecationForParameter (MethodParameter parameter ) {
166
+
167
+ if (this .loggedParameters .putIfAbsent (parameter , Boolean .TRUE ) == null ) {
168
+ var paramName = parameter .getParameterName ();
169
+ this .logger .warn (() -> MESSAGE .formatted (paramName != null ? paramName : "" , parameter ));
170
+ }
171
+ }
172
+
173
+ }
174
+
131
175
}
0 commit comments