diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/DefaultMethodValidator.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/DefaultMethodValidator.java index 6b69851b8a..1e937661a0 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/DefaultMethodValidator.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/DefaultMethodValidator.java @@ -18,6 +18,7 @@ package org.springframework.validation.beanvalidation; import java.lang.reflect.Method; +import org.springframework.core.MethodParameter; import org.springframework.lang.Nullable; /** @@ -44,31 +45,45 @@ public class DefaultMethodValidator implements MethodValidator { } @Override - public void validateArguments(Object target, Method method, Object[] arguments, Class[] groups) { - MethodValidationResult result = this.adapter.validateMethodArguments(target, method, arguments, groups); - handleArgumentsResult(target, method, arguments, groups, result); + public void validateArguments( + Object target, Method method, @Nullable MethodParameter[] parameters, Object[] arguments, + Class[] groups) { + + handleArgumentsValidationResult(target, method, arguments, groups, + this.adapter.validateMethodArguments(target, method, parameters, arguments, groups)); + } + + public void validateReturnValue( + Object target, Method method, @Nullable MethodParameter returnType, @Nullable Object returnValue, + Class[] groups) { + + handleReturnValueValidationResult(target, method, returnValue, groups, + this.adapter.validateMethodReturnValue(target, method, returnType, returnValue, groups)); } /** * Subclasses can override this to handle the result of argument validation. * By default, {@link MethodValidationResult#throwIfViolationsPresent()} is called. + * @param bean the target Object for method invocation + * @param method the target method + * @param arguments the candidate argument values to validate + * @param groups groups for validation determined via */ - protected void handleArgumentsResult( + protected void handleArgumentsValidationResult( Object bean, Method method, Object[] arguments, Class[] groups, MethodValidationResult result) { result.throwIfViolationsPresent(); } - public void validateReturnValue(Object target, Method method, @Nullable Object returnValue, Class[] groups) { - MethodValidationResult result = this.adapter.validateMethodReturnValue(target, method, returnValue, groups); - handleReturnValueResult(target, method, returnValue, groups, result); - } - /** * Subclasses can override this to handle the result of return value validation. * By default, {@link MethodValidationResult#throwIfViolationsPresent()} is called. + * @param bean the target Object for method invocation + * @param method the target method + * @param returnValue the return value to validate + * @param groups groups for validation determined via */ - protected void handleReturnValueResult( + protected void handleReturnValueValidationResult( Object bean, Method method, @Nullable Object returnValue, Class[] groups, MethodValidationResult result) { result.throwIfViolationsPresent(); diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java index 5bc071405c..7ea1936672 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java @@ -176,8 +176,8 @@ public class MethodValidationAdapter { /** * Use this method determine the validation groups to pass into - * {@link #validateMethodArguments(Object, Method, Object[], Class[])} and - * {@link #validateMethodReturnValue(Object, Method, Object, Class[])}. + * {@link #validateMethodArguments(Object, Method, MethodParameter[], Object[], Class[])} and + * {@link #validateMethodReturnValue(Object, Method, MethodParameter, Object, Class[])}. *

Default are the validation groups as specified in the {@link Validated} * annotation on the method, or on the containing target class of the method, * or for an AOP proxy without a target (with all behavior in advisors), also @@ -208,7 +208,8 @@ public class MethodValidationAdapter { * Validate the given method arguments and return the result of validation. * @param target the target Object * @param method the target method - * @param arguments candidate arguments for a method invocation + * @param parameters the parameters, if already created and available + * @param arguments the candidate argument values to validate * @param groups groups for validation determined via * {@link #determineValidationGroups(Object, Method)} * @return a result with {@link ConstraintViolation violations} and @@ -216,7 +217,8 @@ public class MethodValidationAdapter { * in case there are no violations */ public MethodValidationResult validateMethodArguments( - Object target, Method method, Object[] arguments, Class[] groups) { + Object target, Method method, @Nullable MethodParameter[] parameters, Object[] arguments, + Class[] groups) { ExecutableValidator execVal = this.validator.get().forExecutables(); Set> result; @@ -231,14 +233,18 @@ public class MethodValidationAdapter { result = execVal.validateParameters(target, bridgedMethod, arguments, groups); } return (result.isEmpty() ? EMPTY_RESULT : - createException(target, method, result, i -> arguments[i], false)); + createException(target, method, result, + i -> parameters != null ? parameters[i] : new MethodParameter(method, i), + i -> arguments[i], + false)); } /** * Validate the given return value and return the result of validation. * @param target the target Object * @param method the target method - * @param returnValue value returned from invoking the target method + * @param returnType the return parameter, if already created and available + * @param returnValue the return value to validate * @param groups groups for validation determined via * {@link #determineValidationGroups(Object, Method)} * @return a result with {@link ConstraintViolation violations} and @@ -246,16 +252,22 @@ public class MethodValidationAdapter { * in case there are no violations */ public MethodValidationResult validateMethodReturnValue( - Object target, Method method, @Nullable Object returnValue, Class[] groups) { + Object target, Method method, @Nullable MethodParameter returnType, @Nullable Object returnValue, + Class[] groups) { ExecutableValidator execVal = this.validator.get().forExecutables(); Set> result = execVal.validateReturnValue(target, method, returnValue, groups); - return (result.isEmpty() ? EMPTY_RESULT : createException(target, method, result, i -> returnValue, true)); + return (result.isEmpty() ? EMPTY_RESULT : + createException(target, method, result, + i -> returnType != null ? returnType : new MethodParameter(method, -1), + i -> returnValue, + true)); } private MethodValidationException createException( Object target, Method method, Set> violations, - Function argumentFunction, boolean forReturnValue) { + Function parameterFunction, Function argumentFunction, + boolean forReturnValue) { Map parameterViolations = new LinkedHashMap<>(); Map cascadedViolations = new LinkedHashMap<>(); @@ -267,10 +279,11 @@ public class MethodValidationAdapter { MethodParameter parameter; if (node.getKind().equals(ElementKind.PARAMETER)) { - parameter = new MethodParameter(method, node.as(Path.ParameterNode.class).getParameterIndex()); + int index = node.as(Path.ParameterNode.class).getParameterIndex(); + parameter = parameterFunction.apply(index); } else if (node.getKind().equals(ElementKind.RETURN_VALUE)) { - parameter = new MethodParameter(method, -1); + parameter = parameterFunction.apply(-1); } else { continue; diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java index e37b688230..c596463a52 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java @@ -104,12 +104,12 @@ public class MethodValidationInterceptor implements MethodInterceptor { Method method = invocation.getMethod(); Class[] groups = determineValidationGroups(invocation); - this.delegate.validateMethodArguments(target, method, invocation.getArguments(), groups) + this.delegate.validateMethodArguments(target, method, null, invocation.getArguments(), groups) .throwIfViolationsPresent(); Object returnValue = invocation.proceed(); - this.delegate.validateMethodReturnValue(target, method, returnValue, groups) + this.delegate.validateMethodReturnValue(target, method, null, returnValue, groups) .throwIfViolationsPresent(); return returnValue; diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidator.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidator.java index 3109aad494..d549422a5e 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidator.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidator.java @@ -18,6 +18,7 @@ package org.springframework.validation.beanvalidation; import java.lang.reflect.Method; +import org.springframework.core.MethodParameter; import org.springframework.lang.Nullable; /** @@ -35,8 +36,8 @@ public interface MethodValidator { /** * Use this method determine the validation groups to pass into - * {@link #validateArguments(Object, Method, Object[], Class[])} and - * {@link #validateReturnValue(Object, Method, Object, Class[])}. + * {@link #validateArguments(Object, Method, MethodParameter[], Object[], Class[])} and + * {@link #validateReturnValue(Object, Method, MethodParameter, Object, Class[])}. * @param target the target Object * @param method the target method * @return the applicable validation groups as a {@code Class} array @@ -48,24 +49,30 @@ public interface MethodValidator { * Validate the given method arguments and return the result of validation. * @param target the target Object * @param method the target method - * @param arguments candidate arguments for a method invocation + * @param parameters the parameters, if already created and available + * @param arguments the candidate argument values to validate * @param groups groups for validation determined via * {@link #determineValidationGroups(Object, Method)} * @throws MethodValidationException should be raised in case of validation * errors unless the implementation handles those errors otherwise (e.g. * by injecting {@code BindingResult} into the method). */ - void validateArguments(Object target, Method method, Object[] arguments, Class[] groups); + void validateArguments( + Object target, Method method, @Nullable MethodParameter[] parameters, Object[] arguments, + Class[] groups); /** * Validate the given return value and return the result of validation. * @param target the target Object * @param method the target method - * @param returnValue value returned from invoking the target method + * @param returnType the return parameter, if already created and available + * @param returnValue the return value to validate * @param groups groups for validation determined via * {@link #determineValidationGroups(Object, Method)} * @throws MethodValidationException in case of validation errors */ - void validateReturnValue(Object target, Method method, @Nullable Object returnValue, Class[] groups); + void validateReturnValue( + Object target, Method method, @Nullable MethodParameter returnType, @Nullable Object returnValue, + Class[] groups); } diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationAdapterTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationAdapterTests.java index 239abf0d79..fe98f8cd73 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationAdapterTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationAdapterTests.java @@ -181,14 +181,14 @@ public class MethodValidationAdapterTests { Object target, Method method, Object[] arguments, Consumer assertions) { assertions.accept( - this.validationAdapter.validateMethodArguments(target, method, arguments, new Class[0])); + this.validationAdapter.validateMethodArguments(target, method, null, arguments, new Class[0])); } private void validateReturnValue( Object target, Method method, @Nullable Object returnValue, Consumer assertions) { assertions.accept( - this.validationAdapter.validateMethodReturnValue(target, method, returnValue, new Class[0])); + this.validationAdapter.validateMethodReturnValue(target, method, null, returnValue, new Class[0])); } private static void assertBeanResult( diff --git a/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodValidator.java b/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodValidator.java index f04efcb270..862fe44683 100644 --- a/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodValidator.java +++ b/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodValidator.java @@ -46,28 +46,16 @@ import org.springframework.web.method.annotation.ModelFactory; * @author Rossen Stoyanchev * @since 6.1 */ -public class HandlerMethodValidator extends DefaultMethodValidator { +public final class HandlerMethodValidator extends DefaultMethodValidator { - public HandlerMethodValidator(MethodValidationAdapter adapter) { + private HandlerMethodValidator(MethodValidationAdapter adapter) { super(adapter); - adapter.setBindingResultNameResolver(this::determineObjectName); - } - - private String determineObjectName(MethodParameter param, @Nullable Object argument) { - if (param.hasParameterAnnotation(RequestBody.class) || param.hasParameterAnnotation(RequestPart.class)) { - return Conventions.getVariableNameForParameter(param); - } - else { - return ((param.getParameterIndex() != -1) ? - ModelFactory.getNameForParameter(param) : - ModelFactory.getNameForReturnValue(argument, param)); - } } @Override - protected void handleArgumentsResult( + protected void handleArgumentsValidationResult( Object bean, Method method, Object[] arguments, Class[] groups, MethodValidationResult result) { if (result.getConstraintViolations().isEmpty()) { @@ -93,12 +81,21 @@ public class HandlerMethodValidator extends DefaultMethodValidator { result.throwIfViolationsPresent(); } + private String determineObjectName(MethodParameter param, @Nullable Object argument) { + if (param.hasParameterAnnotation(RequestBody.class) || param.hasParameterAnnotation(RequestPart.class)) { + return Conventions.getVariableNameForParameter(param); + } + else { + return ((param.getParameterIndex() != -1) ? + ModelFactory.getNameForParameter(param) : + ModelFactory.getNameForReturnValue(argument, param)); + } + } + /** - * Create a {@link MethodValidator} if Bean Validation is enabled in Spring MVC or WebFlux. - * @param bindingInitializer for the configured Validator and MessageCodesResolver - * @param parameterNameDiscoverer the {@code ParameterNameDiscoverer} to use - * for {@link MethodValidationAdapter#setParameterNameDiscoverer} + * Static factory method to create a {@link HandlerMethodValidator} if Bean + * Validation is enabled in Spring MVC or WebFlux. */ @Nullable public static MethodValidator from( @@ -107,15 +104,17 @@ public class HandlerMethodValidator extends DefaultMethodValidator { if (bindingInitializer instanceof ConfigurableWebBindingInitializer configurableInitializer) { if (configurableInitializer.getValidator() instanceof Validator validator) { - MethodValidationAdapter validationAdapter = new MethodValidationAdapter(validator); + MethodValidationAdapter adapter = new MethodValidationAdapter(validator); if (parameterNameDiscoverer != null) { - validationAdapter.setParameterNameDiscoverer(parameterNameDiscoverer); + adapter.setParameterNameDiscoverer(parameterNameDiscoverer); } MessageCodesResolver codesResolver = configurableInitializer.getMessageCodesResolver(); if (codesResolver != null) { - validationAdapter.setMessageCodesResolver(codesResolver); + adapter.setMessageCodesResolver(codesResolver); } - return new HandlerMethodValidator(validationAdapter); + HandlerMethodValidator methodValidator = new HandlerMethodValidator(adapter); + adapter.setBindingResultNameResolver(methodValidator::determineObjectName); + return methodValidator; } } return null; diff --git a/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java index f2c38698df..58a7d8a19e 100644 --- a/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java +++ b/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java @@ -168,13 +168,15 @@ public class InvocableHandlerMethod extends HandlerMethod { Class[] groups = getValidationGroups(); if (shouldValidateArguments() && this.methodValidator != null) { - this.methodValidator.validateArguments(getBean(), getBridgedMethod(), args, groups); + this.methodValidator.validateArguments( + getBean(), getBridgedMethod(), getMethodParameters(), args, groups); } Object returnValue = doInvoke(args); if (shouldValidateReturnValue() && this.methodValidator != null) { - this.methodValidator.validateReturnValue(getBean(), getBridgedMethod(), returnValue, groups); + this.methodValidator.validateReturnValue( + getBean(), getBridgedMethod(), getReturnType(), returnValue, groups); } return returnValue; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java index 8fd390ef9a..44d564d31c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java @@ -152,7 +152,8 @@ public class InvocableHandlerMethod extends HandlerMethod { return getMethodArgumentValues(exchange, bindingContext, providedArgs).flatMap(args -> { Class[] groups = getValidationGroups(); if (shouldValidateArguments() && this.methodValidator != null) { - this.methodValidator.validateArguments(getBean(), getBridgedMethod(), args, groups); + this.methodValidator.validateArguments( + getBean(), getBridgedMethod(), getMethodParameters(), args, groups); } Object value; Method method = getBridgedMethod();