1
1
/*
2
- * Copyright 2002-2022 the original author or authors.
2
+ * Copyright 2002-2023 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
22
22
import java .util .Arrays ;
23
23
import java .util .List ;
24
24
import java .util .StringJoiner ;
25
+ import java .util .function .Predicate ;
25
26
import java .util .stream .Collectors ;
26
27
import java .util .stream .IntStream ;
27
28
35
36
import org .springframework .core .MethodParameter ;
36
37
import org .springframework .core .ResolvableType ;
37
38
import org .springframework .core .annotation .AnnotatedElementUtils ;
39
+ import org .springframework .core .annotation .AnnotationUtils ;
40
+ import org .springframework .core .annotation .MergedAnnotation ;
41
+ import org .springframework .core .annotation .MergedAnnotationPredicates ;
42
+ import org .springframework .core .annotation .MergedAnnotations ;
38
43
import org .springframework .core .annotation .SynthesizingMethodParameter ;
39
44
import org .springframework .http .HttpStatusCode ;
40
45
import org .springframework .lang .NonNull ;
44
49
import org .springframework .util .ObjectUtils ;
45
50
import org .springframework .util .ReflectionUtils ;
46
51
import org .springframework .util .StringUtils ;
52
+ import org .springframework .validation .annotation .Validated ;
47
53
import org .springframework .web .bind .annotation .ResponseStatus ;
48
54
49
55
/**
@@ -84,6 +90,10 @@ public class HandlerMethod {
84
90
85
91
private final MethodParameter [] parameters ;
86
92
93
+ private final boolean validateArguments ;
94
+
95
+ private final boolean validateReturnValue ;
96
+
87
97
@ Nullable
88
98
private HttpStatusCode responseStatus ;
89
99
@@ -122,6 +132,8 @@ protected HandlerMethod(Object bean, Method method, @Nullable MessageSource mess
122
132
this .bridgedMethod = BridgeMethodResolver .findBridgedMethod (method );
123
133
ReflectionUtils .makeAccessible (this .bridgedMethod );
124
134
this .parameters = initMethodParameters ();
135
+ this .validateArguments = MethodValidationInitializer .checkArguments (this .beanType , this .parameters );
136
+ this .validateReturnValue = MethodValidationInitializer .checkReturnValue (this .beanType , this .bridgedMethod );
125
137
evaluateResponseStatus ();
126
138
this .description = initDescription (this .beanType , this .method );
127
139
}
@@ -141,6 +153,8 @@ public HandlerMethod(Object bean, String methodName, Class<?>... parameterTypes)
141
153
this .bridgedMethod = BridgeMethodResolver .findBridgedMethod (this .method );
142
154
ReflectionUtils .makeAccessible (this .bridgedMethod );
143
155
this .parameters = initMethodParameters ();
156
+ this .validateArguments = MethodValidationInitializer .checkArguments (this .beanType , this .parameters );
157
+ this .validateReturnValue = MethodValidationInitializer .checkReturnValue (this .beanType , this .bridgedMethod );
144
158
evaluateResponseStatus ();
145
159
this .description = initDescription (this .beanType , this .method );
146
160
}
@@ -177,6 +191,8 @@ public HandlerMethod(
177
191
this .bridgedMethod = BridgeMethodResolver .findBridgedMethod (method );
178
192
ReflectionUtils .makeAccessible (this .bridgedMethod );
179
193
this .parameters = initMethodParameters ();
194
+ this .validateArguments = MethodValidationInitializer .checkArguments (this .beanType , this .parameters );
195
+ this .validateReturnValue = MethodValidationInitializer .checkReturnValue (this .beanType , this .bridgedMethod );
180
196
evaluateResponseStatus ();
181
197
this .description = initDescription (this .beanType , this .method );
182
198
}
@@ -193,6 +209,8 @@ protected HandlerMethod(HandlerMethod handlerMethod) {
193
209
this .method = handlerMethod .method ;
194
210
this .bridgedMethod = handlerMethod .bridgedMethod ;
195
211
this .parameters = handlerMethod .parameters ;
212
+ this .validateArguments = handlerMethod .validateArguments ;
213
+ this .validateReturnValue = handlerMethod .validateReturnValue ;
196
214
this .responseStatus = handlerMethod .responseStatus ;
197
215
this .responseStatusReason = handlerMethod .responseStatusReason ;
198
216
this .description = handlerMethod .description ;
@@ -212,6 +230,8 @@ private HandlerMethod(HandlerMethod handlerMethod, Object handler) {
212
230
this .method = handlerMethod .method ;
213
231
this .bridgedMethod = handlerMethod .bridgedMethod ;
214
232
this .parameters = handlerMethod .parameters ;
233
+ this .validateArguments = handlerMethod .validateArguments ;
234
+ this .validateReturnValue = handlerMethod .validateReturnValue ;
215
235
this .responseStatus = handlerMethod .responseStatus ;
216
236
this .responseStatusReason = handlerMethod .responseStatusReason ;
217
237
this .resolvedFromHandlerMethod = handlerMethod ;
@@ -290,6 +310,33 @@ public MethodParameter[] getMethodParameters() {
290
310
return this .parameters ;
291
311
}
292
312
313
+ /**
314
+ * Whether the method arguments are a candidate for method validation, which
315
+ * is the case when there are parameter {@code jakarta.validation.Constraint}
316
+ * annotations.
317
+ * <p>The presence of {@code jakarta.validation.Valid} by itself does not
318
+ * trigger method validation since such parameters are already validated at
319
+ * the level of argument resolvers.
320
+ * <p><strong>Note:</strong> if the class is annotated with {@link Validated},
321
+ * this method returns false, deferring to method validation via AOP proxy.
322
+ * @since 6.1
323
+ */
324
+ public boolean shouldValidateArguments () {
325
+ return this .validateArguments ;
326
+ }
327
+
328
+ /**
329
+ * Whether the method return value is a candidate for method validation, which
330
+ * is the case when there are method {@code jakarta.validation.Constraint}
331
+ * or {@code jakarta.validation.Valid} annotations.
332
+ * <p><strong>Note:</strong> if the class is annotated with {@link Validated},
333
+ * this method returns false, deferring to method validation via AOP proxy.
334
+ * @since 6.1
335
+ */
336
+ public boolean shouldValidateReturnValue () {
337
+ return this .validateReturnValue ;
338
+ }
339
+
293
340
/**
294
341
* Return the specified response status, if any.
295
342
* @since 4.3.8
@@ -603,4 +650,38 @@ public ReturnValueMethodParameter clone() {
603
650
}
604
651
}
605
652
653
+
654
+ /**
655
+ * Checks for the presence of {@code @Constraint} and {@code @Valid}
656
+ * annotations on the method and method parameters.
657
+ */
658
+ private static class MethodValidationInitializer {
659
+
660
+ private static final Predicate <MergedAnnotation <? extends Annotation >> INPUT_PREDICATE =
661
+ MergedAnnotationPredicates .typeIn ("jakarta.validation.Constraint" );
662
+
663
+ private static final Predicate <MergedAnnotation <? extends Annotation >> OUTPUT_PREDICATE =
664
+ MergedAnnotationPredicates .typeIn ("jakarta.validation.Valid" , "jakarta.validation.Constraint" );
665
+
666
+ public static boolean checkArguments (Class <?> beanType , MethodParameter [] parameters ) {
667
+ if (AnnotationUtils .findAnnotation (beanType , Validated .class ) == null ) {
668
+ for (MethodParameter parameter : parameters ) {
669
+ MergedAnnotations merged = MergedAnnotations .from (parameter .getParameterAnnotations ());
670
+ if (merged .stream ().anyMatch (INPUT_PREDICATE )) {
671
+ return true ;
672
+ }
673
+ }
674
+ }
675
+ return false ;
676
+ }
677
+
678
+ public static boolean checkReturnValue (Class <?> beanType , Method method ) {
679
+ if (AnnotationUtils .findAnnotation (beanType , Validated .class ) == null ) {
680
+ MergedAnnotations merged = MergedAnnotations .from (method , MergedAnnotations .SearchStrategy .TYPE_HIERARCHY );
681
+ return merged .stream ().anyMatch (OUTPUT_PREDICATE );
682
+ }
683
+ return false ;
684
+ }
685
+ }
686
+
606
687
}
0 commit comments