Skip to content

Commit 85d8102

Browse files
committed
Add MethodParameter[] input to MethodValidationAdapter
This allows re-use of existing MethodParameter instances from controller methods with cached metadata, and also ensures additional capabilities such as looking up parameter annotations on interfaces. See gh-29825
1 parent e7c3e1c commit 85d8102

File tree

8 files changed

+94
-57
lines changed

8 files changed

+94
-57
lines changed

Diff for: spring-context/src/main/java/org/springframework/validation/beanvalidation/DefaultMethodValidator.java

+25-10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.lang.reflect.Method;
2020

21+
import org.springframework.core.MethodParameter;
2122
import org.springframework.lang.Nullable;
2223

2324
/**
@@ -44,31 +45,45 @@ public Class<?>[] determineValidationGroups(Object bean, Method method) {
4445
}
4546

4647
@Override
47-
public void validateArguments(Object target, Method method, Object[] arguments, Class<?>[] groups) {
48-
MethodValidationResult result = this.adapter.validateMethodArguments(target, method, arguments, groups);
49-
handleArgumentsResult(target, method, arguments, groups, result);
48+
public void validateArguments(
49+
Object target, Method method, @Nullable MethodParameter[] parameters, Object[] arguments,
50+
Class<?>[] groups) {
51+
52+
handleArgumentsValidationResult(target, method, arguments, groups,
53+
this.adapter.validateMethodArguments(target, method, parameters, arguments, groups));
54+
}
55+
56+
public void validateReturnValue(
57+
Object target, Method method, @Nullable MethodParameter returnType, @Nullable Object returnValue,
58+
Class<?>[] groups) {
59+
60+
handleReturnValueValidationResult(target, method, returnValue, groups,
61+
this.adapter.validateMethodReturnValue(target, method, returnType, returnValue, groups));
5062
}
5163

5264
/**
5365
* Subclasses can override this to handle the result of argument validation.
5466
* By default, {@link MethodValidationResult#throwIfViolationsPresent()} is called.
67+
* @param bean the target Object for method invocation
68+
* @param method the target method
69+
* @param arguments the candidate argument values to validate
70+
* @param groups groups for validation determined via
5571
*/
56-
protected void handleArgumentsResult(
72+
protected void handleArgumentsValidationResult(
5773
Object bean, Method method, Object[] arguments, Class<?>[] groups, MethodValidationResult result) {
5874

5975
result.throwIfViolationsPresent();
6076
}
6177

62-
public void validateReturnValue(Object target, Method method, @Nullable Object returnValue, Class<?>[] groups) {
63-
MethodValidationResult result = this.adapter.validateMethodReturnValue(target, method, returnValue, groups);
64-
handleReturnValueResult(target, method, returnValue, groups, result);
65-
}
66-
6778
/**
6879
* Subclasses can override this to handle the result of return value validation.
6980
* By default, {@link MethodValidationResult#throwIfViolationsPresent()} is called.
81+
* @param bean the target Object for method invocation
82+
* @param method the target method
83+
* @param returnValue the return value to validate
84+
* @param groups groups for validation determined via
7085
*/
71-
protected void handleReturnValueResult(
86+
protected void handleReturnValueValidationResult(
7287
Object bean, Method method, @Nullable Object returnValue, Class<?>[] groups, MethodValidationResult result) {
7388

7489
result.throwIfViolationsPresent();

Diff for: spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java

+24-11
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,8 @@ public void setBindingResultNameResolver(BindingResultNameResolver nameResolver)
176176

177177
/**
178178
* Use this method determine the validation groups to pass into
179-
* {@link #validateMethodArguments(Object, Method, Object[], Class[])} and
180-
* {@link #validateMethodReturnValue(Object, Method, Object, Class[])}.
179+
* {@link #validateMethodArguments(Object, Method, MethodParameter[], Object[], Class[])} and
180+
* {@link #validateMethodReturnValue(Object, Method, MethodParameter, Object, Class[])}.
181181
* <p>Default are the validation groups as specified in the {@link Validated}
182182
* annotation on the method, or on the containing target class of the method,
183183
* or for an AOP proxy without a target (with all behavior in advisors), also
@@ -208,15 +208,17 @@ public static Class<?>[] determineValidationGroups(Object target, Method method)
208208
* Validate the given method arguments and return the result of validation.
209209
* @param target the target Object
210210
* @param method the target method
211-
* @param arguments candidate arguments for a method invocation
211+
* @param parameters the parameters, if already created and available
212+
* @param arguments the candidate argument values to validate
212213
* @param groups groups for validation determined via
213214
* {@link #determineValidationGroups(Object, Method)}
214215
* @return a result with {@link ConstraintViolation violations} and
215216
* {@link ParameterValidationResult validationResults}, both possibly empty
216217
* in case there are no violations
217218
*/
218219
public MethodValidationResult validateMethodArguments(
219-
Object target, Method method, Object[] arguments, Class<?>[] groups) {
220+
Object target, Method method, @Nullable MethodParameter[] parameters, Object[] arguments,
221+
Class<?>[] groups) {
220222

221223
ExecutableValidator execVal = this.validator.get().forExecutables();
222224
Set<ConstraintViolation<Object>> result;
@@ -231,31 +233,41 @@ public MethodValidationResult validateMethodArguments(
231233
result = execVal.validateParameters(target, bridgedMethod, arguments, groups);
232234
}
233235
return (result.isEmpty() ? EMPTY_RESULT :
234-
createException(target, method, result, i -> arguments[i], false));
236+
createException(target, method, result,
237+
i -> parameters != null ? parameters[i] : new MethodParameter(method, i),
238+
i -> arguments[i],
239+
false));
235240
}
236241

237242
/**
238243
* Validate the given return value and return the result of validation.
239244
* @param target the target Object
240245
* @param method the target method
241-
* @param returnValue value returned from invoking the target method
246+
* @param returnType the return parameter, if already created and available
247+
* @param returnValue the return value to validate
242248
* @param groups groups for validation determined via
243249
* {@link #determineValidationGroups(Object, Method)}
244250
* @return a result with {@link ConstraintViolation violations} and
245251
* {@link ParameterValidationResult validationResults}, both possibly empty
246252
* in case there are no violations
247253
*/
248254
public MethodValidationResult validateMethodReturnValue(
249-
Object target, Method method, @Nullable Object returnValue, Class<?>[] groups) {
255+
Object target, Method method, @Nullable MethodParameter returnType, @Nullable Object returnValue,
256+
Class<?>[] groups) {
250257

251258
ExecutableValidator execVal = this.validator.get().forExecutables();
252259
Set<ConstraintViolation<Object>> result = execVal.validateReturnValue(target, method, returnValue, groups);
253-
return (result.isEmpty() ? EMPTY_RESULT : createException(target, method, result, i -> returnValue, true));
260+
return (result.isEmpty() ? EMPTY_RESULT :
261+
createException(target, method, result,
262+
i -> returnType != null ? returnType : new MethodParameter(method, -1),
263+
i -> returnValue,
264+
true));
254265
}
255266

256267
private MethodValidationException createException(
257268
Object target, Method method, Set<ConstraintViolation<Object>> violations,
258-
Function<Integer, Object> argumentFunction, boolean forReturnValue) {
269+
Function<Integer, MethodParameter> parameterFunction, Function<Integer, Object> argumentFunction,
270+
boolean forReturnValue) {
259271

260272
Map<MethodParameter, ValueResultBuilder> parameterViolations = new LinkedHashMap<>();
261273
Map<Path.Node, BeanResultBuilder> cascadedViolations = new LinkedHashMap<>();
@@ -267,10 +279,11 @@ private MethodValidationException createException(
267279

268280
MethodParameter parameter;
269281
if (node.getKind().equals(ElementKind.PARAMETER)) {
270-
parameter = new MethodParameter(method, node.as(Path.ParameterNode.class).getParameterIndex());
282+
int index = node.as(Path.ParameterNode.class).getParameterIndex();
283+
parameter = parameterFunction.apply(index);
271284
}
272285
else if (node.getKind().equals(ElementKind.RETURN_VALUE)) {
273-
parameter = new MethodParameter(method, -1);
286+
parameter = parameterFunction.apply(-1);
274287
}
275288
else {
276289
continue;

Diff for: spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,12 @@ public Object invoke(MethodInvocation invocation) throws Throwable {
104104
Method method = invocation.getMethod();
105105
Class<?>[] groups = determineValidationGroups(invocation);
106106

107-
this.delegate.validateMethodArguments(target, method, invocation.getArguments(), groups)
107+
this.delegate.validateMethodArguments(target, method, null, invocation.getArguments(), groups)
108108
.throwIfViolationsPresent();
109109

110110
Object returnValue = invocation.proceed();
111111

112-
this.delegate.validateMethodReturnValue(target, method, returnValue, groups)
112+
this.delegate.validateMethodReturnValue(target, method, null, returnValue, groups)
113113
.throwIfViolationsPresent();
114114

115115
return returnValue;

Diff for: spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidator.java

+13-6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.lang.reflect.Method;
2020

21+
import org.springframework.core.MethodParameter;
2122
import org.springframework.lang.Nullable;
2223

2324
/**
@@ -35,8 +36,8 @@ public interface MethodValidator {
3536

3637
/**
3738
* Use this method determine the validation groups to pass into
38-
* {@link #validateArguments(Object, Method, Object[], Class[])} and
39-
* {@link #validateReturnValue(Object, Method, Object, Class[])}.
39+
* {@link #validateArguments(Object, Method, MethodParameter[], Object[], Class[])} and
40+
* {@link #validateReturnValue(Object, Method, MethodParameter, Object, Class[])}.
4041
* @param target the target Object
4142
* @param method the target method
4243
* @return the applicable validation groups as a {@code Class} array
@@ -48,24 +49,30 @@ public interface MethodValidator {
4849
* Validate the given method arguments and return the result of validation.
4950
* @param target the target Object
5051
* @param method the target method
51-
* @param arguments candidate arguments for a method invocation
52+
* @param parameters the parameters, if already created and available
53+
* @param arguments the candidate argument values to validate
5254
* @param groups groups for validation determined via
5355
* {@link #determineValidationGroups(Object, Method)}
5456
* @throws MethodValidationException should be raised in case of validation
5557
* errors unless the implementation handles those errors otherwise (e.g.
5658
* by injecting {@code BindingResult} into the method).
5759
*/
58-
void validateArguments(Object target, Method method, Object[] arguments, Class<?>[] groups);
60+
void validateArguments(
61+
Object target, Method method, @Nullable MethodParameter[] parameters, Object[] arguments,
62+
Class<?>[] groups);
5963

6064
/**
6165
* Validate the given return value and return the result of validation.
6266
* @param target the target Object
6367
* @param method the target method
64-
* @param returnValue value returned from invoking the target method
68+
* @param returnType the return parameter, if already created and available
69+
* @param returnValue the return value to validate
6570
* @param groups groups for validation determined via
6671
* {@link #determineValidationGroups(Object, Method)}
6772
* @throws MethodValidationException in case of validation errors
6873
*/
69-
void validateReturnValue(Object target, Method method, @Nullable Object returnValue, Class<?>[] groups);
74+
void validateReturnValue(
75+
Object target, Method method, @Nullable MethodParameter returnType, @Nullable Object returnValue,
76+
Class<?>[] groups);
7077

7178
}

Diff for: spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationAdapterTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -181,14 +181,14 @@ private void validateArguments(
181181
Object target, Method method, Object[] arguments, Consumer<MethodValidationResult> assertions) {
182182

183183
assertions.accept(
184-
this.validationAdapter.validateMethodArguments(target, method, arguments, new Class<?>[0]));
184+
this.validationAdapter.validateMethodArguments(target, method, null, arguments, new Class<?>[0]));
185185
}
186186

187187
private void validateReturnValue(
188188
Object target, Method method, @Nullable Object returnValue, Consumer<MethodValidationResult> assertions) {
189189

190190
assertions.accept(
191-
this.validationAdapter.validateMethodReturnValue(target, method, returnValue, new Class<?>[0]));
191+
this.validationAdapter.validateMethodReturnValue(target, method, null, returnValue, new Class<?>[0]));
192192
}
193193

194194
private static void assertBeanResult(

Diff for: spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodValidator.java

+22-23
Original file line numberDiff line numberDiff line change
@@ -46,28 +46,16 @@
4646
* @author Rossen Stoyanchev
4747
* @since 6.1
4848
*/
49-
public class HandlerMethodValidator extends DefaultMethodValidator {
49+
public final class HandlerMethodValidator extends DefaultMethodValidator {
5050

5151

52-
public HandlerMethodValidator(MethodValidationAdapter adapter) {
52+
private HandlerMethodValidator(MethodValidationAdapter adapter) {
5353
super(adapter);
54-
adapter.setBindingResultNameResolver(this::determineObjectName);
55-
}
56-
57-
private String determineObjectName(MethodParameter param, @Nullable Object argument) {
58-
if (param.hasParameterAnnotation(RequestBody.class) || param.hasParameterAnnotation(RequestPart.class)) {
59-
return Conventions.getVariableNameForParameter(param);
60-
}
61-
else {
62-
return ((param.getParameterIndex() != -1) ?
63-
ModelFactory.getNameForParameter(param) :
64-
ModelFactory.getNameForReturnValue(argument, param));
65-
}
6654
}
6755

6856

6957
@Override
70-
protected void handleArgumentsResult(
58+
protected void handleArgumentsValidationResult(
7159
Object bean, Method method, Object[] arguments, Class<?>[] groups, MethodValidationResult result) {
7260

7361
if (result.getConstraintViolations().isEmpty()) {
@@ -93,12 +81,21 @@ protected void handleArgumentsResult(
9381
result.throwIfViolationsPresent();
9482
}
9583

84+
private String determineObjectName(MethodParameter param, @Nullable Object argument) {
85+
if (param.hasParameterAnnotation(RequestBody.class) || param.hasParameterAnnotation(RequestPart.class)) {
86+
return Conventions.getVariableNameForParameter(param);
87+
}
88+
else {
89+
return ((param.getParameterIndex() != -1) ?
90+
ModelFactory.getNameForParameter(param) :
91+
ModelFactory.getNameForReturnValue(argument, param));
92+
}
93+
}
94+
9695

9796
/**
98-
* Create a {@link MethodValidator} if Bean Validation is enabled in Spring MVC or WebFlux.
99-
* @param bindingInitializer for the configured Validator and MessageCodesResolver
100-
* @param parameterNameDiscoverer the {@code ParameterNameDiscoverer} to use
101-
* for {@link MethodValidationAdapter#setParameterNameDiscoverer}
97+
* Static factory method to create a {@link HandlerMethodValidator} if Bean
98+
* Validation is enabled in Spring MVC or WebFlux.
10299
*/
103100
@Nullable
104101
public static MethodValidator from(
@@ -107,15 +104,17 @@ public static MethodValidator from(
107104

108105
if (bindingInitializer instanceof ConfigurableWebBindingInitializer configurableInitializer) {
109106
if (configurableInitializer.getValidator() instanceof Validator validator) {
110-
MethodValidationAdapter validationAdapter = new MethodValidationAdapter(validator);
107+
MethodValidationAdapter adapter = new MethodValidationAdapter(validator);
111108
if (parameterNameDiscoverer != null) {
112-
validationAdapter.setParameterNameDiscoverer(parameterNameDiscoverer);
109+
adapter.setParameterNameDiscoverer(parameterNameDiscoverer);
113110
}
114111
MessageCodesResolver codesResolver = configurableInitializer.getMessageCodesResolver();
115112
if (codesResolver != null) {
116-
validationAdapter.setMessageCodesResolver(codesResolver);
113+
adapter.setMessageCodesResolver(codesResolver);
117114
}
118-
return new HandlerMethodValidator(validationAdapter);
115+
HandlerMethodValidator methodValidator = new HandlerMethodValidator(adapter);
116+
adapter.setBindingResultNameResolver(methodValidator::determineObjectName);
117+
return methodValidator;
119118
}
120119
}
121120
return null;

Diff for: spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,15 @@ public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewC
168168

169169
Class<?>[] groups = getValidationGroups();
170170
if (shouldValidateArguments() && this.methodValidator != null) {
171-
this.methodValidator.validateArguments(getBean(), getBridgedMethod(), args, groups);
171+
this.methodValidator.validateArguments(
172+
getBean(), getBridgedMethod(), getMethodParameters(), args, groups);
172173
}
173174

174175
Object returnValue = doInvoke(args);
175176

176177
if (shouldValidateReturnValue() && this.methodValidator != null) {
177-
this.methodValidator.validateReturnValue(getBean(), getBridgedMethod(), returnValue, groups);
178+
this.methodValidator.validateReturnValue(
179+
getBean(), getBridgedMethod(), getReturnType(), returnValue, groups);
178180
}
179181

180182
return returnValue;

Diff for: spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ public Mono<HandlerResult> invoke(
152152
return getMethodArgumentValues(exchange, bindingContext, providedArgs).flatMap(args -> {
153153
Class<?>[] groups = getValidationGroups();
154154
if (shouldValidateArguments() && this.methodValidator != null) {
155-
this.methodValidator.validateArguments(getBean(), getBridgedMethod(), args, groups);
155+
this.methodValidator.validateArguments(
156+
getBean(), getBridgedMethod(), getMethodParameters(), args, groups);
156157
}
157158
Object value;
158159
Method method = getBridgedMethod();

0 commit comments

Comments
 (0)