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 deleted file mode 100644 index 1588056191f..00000000000 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/DefaultMethodValidator.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.validation.beanvalidation; - -import java.lang.reflect.Method; - -import org.springframework.core.MethodParameter; -import org.springframework.lang.Nullable; - -/** - * Default implementation of {@link MethodValidator} that delegates to a - * {@link MethodValidationAdapter}. Also, convenient as a base class that allows - * handling of the validation result. - * - * @author Rossen Stoyanchev - * @since 6.1 - */ -public class DefaultMethodValidator implements MethodValidator { - - private final MethodValidationAdapter adapter; - - - public DefaultMethodValidator(MethodValidationAdapter adapter) { - this.adapter = adapter; - } - - - @Override - public Class[] determineValidationGroups(Object bean, Method method) { - return MethodValidationAdapter.determineValidationGroups(bean, method); - } - - @Override - public void validateArguments( - Object target, Method method, @Nullable MethodParameter[] parameters, - Object[] arguments, Class[] groups) { - - MethodValidationResult validationResult = - this.adapter.validateMethodArguments(target, method, parameters, arguments, groups); - - handleArgumentsResult(arguments, groups, validationResult); - } - - /** - * Subclasses can override this to handle the result of argument validation. - * By default, throws {@link MethodValidationException} if there are errors. - * @param arguments the candidate argument values to validate - * @param groups groups for validation determined via - * @param validationResult the result from validation - */ - protected void handleArgumentsResult( - Object[] arguments, Class[] groups, MethodValidationResult validationResult) { - - if (validationResult.hasViolations()) { - throw MethodValidationException.forResult(validationResult); - } - } - - public void validateReturnValue( - Object target, Method method, @Nullable MethodParameter returnType, - @Nullable Object returnValue, Class[] groups) { - - MethodValidationResult validationResult = - this.adapter.validateMethodReturnValue(target, method, returnType, returnValue, groups); - - handleReturnValueResult(returnValue, groups, validationResult); - } - - /** - * Subclasses can override this to handle the result of return value validation. - * By default, throws {@link MethodValidationException} if there are errors. - * @param returnValue the return value to validate - * @param groups groups for validation determined via - * @param validationResult the result from validation - */ - protected void handleReturnValueResult( - @Nullable Object returnValue, Class[] groups, MethodValidationResult validationResult) { - - if (validationResult.hasViolations()) { - throw MethodValidationException.forResult(validationResult); - } - } - -} 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 2b349651dfb..41aafc07b61 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 @@ -18,6 +18,7 @@ package org.springframework.validation.beanvalidation; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashMap; @@ -49,6 +50,7 @@ import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.function.SingletonSupplier; import org.springframework.validation.BeanPropertyBindingResult; @@ -59,20 +61,19 @@ import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.annotation.Validated; /** - * Assist with applying method-level validation via - * {@link jakarta.validation.Validator}, adapt each resulting - * {@link ConstraintViolation} to {@link ParameterValidationResult}, and - * raise {@link MethodValidationException}. - * - *

Used by {@link MethodValidationInterceptor}. + * {@link MethodValidator} that uses a Bean Validation + * {@link jakarta.validation.Validator} for validation, and adapts + * {@link ConstraintViolation}s to {@link MethodValidationResult}. * * @author Rossen Stoyanchev * @since 6.1 */ -public class MethodValidationAdapter { +public class MethodValidationAdapter implements MethodValidator { private static final Comparator RESULT_COMPARATOR = new ResultComparator(); + private static final MethodValidationResult EMPTY_RESULT = new EmptyMethodValidationResult(); + private final Supplier validator; @@ -158,13 +159,10 @@ public class MethodValidationAdapter { } /** - * Configure a resolver for {@link BindingResult} method parameters to match - * the behavior of the higher level programming model, e.g. how the name of - * {@code @ModelAttribute} or {@code @RequestBody} is determined in Spring MVC. - *

If this is not configured, then {@link #createBindingResult} will apply - * default behavior to resolve the name to use. - * behavior applies. - * @param nameResolver the resolver to use + * Configure a resolver for the name of Object parameters with nested errors + * to allow matching the name used in the higher level programming model, + * e.g. {@code @ModelAttribute} in Spring MVC. + *

If not configured, {@link #createBindingResult} determines the name. */ public void setBindingResultNameResolver(BindingResultNameResolver nameResolver) { this.objectNameResolver = nameResolver; @@ -172,18 +170,14 @@ public class MethodValidationAdapter { /** - * Use this method determine the validation groups to pass into - * {@link #validateMethodArguments(Object, Method, MethodParameter[], Object[], Class[])} and - * {@link #validateMethodReturnValue(Object, Method, MethodParameter, Object, Class[])}. + * {@inheritDoc}. *

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 * check on proxied interfaces. - * @param target the target Object - * @param method the target method - * @return the applicable validation groups as a {@code Class} array */ - public static Class[] determineValidationGroups(Object target, Method method) { + @Override + public Class[] determineValidationGroups(Object target, Method method) { Validated validatedAnn = AnnotationUtils.findAnnotation(method, Validated.class); if (validatedAnn == null) { if (AopUtils.isAopProxy(target)) { @@ -201,72 +195,75 @@ public class MethodValidationAdapter { return (validatedAnn != null ? validatedAnn.value() : new Class[0]); } - /** - * Validate the given method arguments and return the result of validation. - * @param target the target Object - * @param method the target method - * @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 - * {@link ParameterValidationResult validationResults}, both possibly empty - * in case there are no violations - */ - public MethodValidationResult validateMethodArguments( + @Override + public final MethodValidationResult validateArguments( Object target, Method method, @Nullable MethodParameter[] parameters, Object[] arguments, Class[] groups) { + Set> violations = + invokeValidatorForArguments(target, method, arguments, groups); + + if (violations.isEmpty()) { + return EMPTY_RESULT; + } + + return adaptViolations(target, method, violations, + i -> parameters != null ? parameters[i] : new MethodParameter(method, i), + i -> arguments[i]); + } + + /** + * Invoke the validator, and return the resulting violations. + */ + public final Set> invokeValidatorForArguments( + Object target, Method method, Object[] arguments, Class[] groups) { + ExecutableValidator execVal = this.validator.get().forExecutables(); - Set> result; + Set> violations; try { - result = execVal.validateParameters(target, method, arguments, groups); + violations = execVal.validateParameters(target, method, arguments, groups); } catch (IllegalArgumentException ex) { // Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011 // Let's try to find the bridged method on the implementation class... Method mostSpecificMethod = ClassUtils.getMostSpecificMethod(method, target.getClass()); Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(mostSpecificMethod); - result = execVal.validateParameters(target, bridgedMethod, arguments, groups); + violations = execVal.validateParameters(target, bridgedMethod, arguments, groups); } - return (result.isEmpty() ? - MethodValidationException.forEmptyResult(target, method, true) : - createException(target, method, result, - i -> parameters != null ? parameters[i] : new MethodParameter(method, i), - i -> arguments[i], - false)); + return violations; } - /** - * Validate the given return value and return the result of validation. - * @param target the target Object - * @param method 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 - * {@link ParameterValidationResult validationResults}, both possibly empty - * in case there are no violations - */ - public MethodValidationResult validateMethodReturnValue( + @Override + public final MethodValidationResult validateReturnValue( Object target, Method method, @Nullable MethodParameter returnType, @Nullable Object returnValue, Class[] groups) { + Set> violations = + invokeValidatorForReturnValue(target, method, returnValue, groups); + + if (violations.isEmpty()) { + return EMPTY_RESULT; + } + + return adaptViolations(target, method, violations, + i -> returnType != null ? returnType : new MethodParameter(method, -1), + i -> returnValue); + } + + /** + * Invoke the validator, and return the resulting violations. + */ + public final Set> invokeValidatorForReturnValue( + Object target, Method method, @Nullable Object returnValue, Class[] groups) { + ExecutableValidator execVal = this.validator.get().forExecutables(); - Set> result = execVal.validateReturnValue(target, method, returnValue, groups); - return (result.isEmpty() ? - MethodValidationException.forEmptyResult(target, method, true) : - createException(target, method, result, - i -> returnType != null ? returnType : new MethodParameter(method, -1), - i -> returnValue, - true)); + return execVal.validateReturnValue(target, method, returnValue, groups); } - private MethodValidationException createException( + private MethodValidationResult adaptViolations( Object target, Method method, Set> violations, - Function parameterFunction, Function argumentFunction, - boolean forReturnValue) { + Function parameterFunction, + Function argumentFunction) { Map parameterViolations = new LinkedHashMap<>(); Map cascadedViolations = new LinkedHashMap<>(); @@ -309,7 +306,7 @@ public class MethodValidationAdapter { cascadedViolations.forEach((node, builder) -> validatonResultList.add(builder.build())); validatonResultList.sort(RESULT_COMPARATOR); - return new MethodValidationException(target, method, forReturnValue, violations, validatonResultList); + return new DefaultMethodValidationResult(target, method, validatonResultList); } /** @@ -412,8 +409,6 @@ public class MethodValidationAdapter { private final List resolvableErrors = new ArrayList<>(); - private final List> violations = new ArrayList<>(); - public ValueResultBuilder(Object target, MethodParameter parameter, @Nullable Object argument) { this.target = target; this.parameter = parameter; @@ -422,12 +417,10 @@ public class MethodValidationAdapter { public void addViolation(ConstraintViolation violation) { this.resolvableErrors.add(createMessageSourceResolvable(this.target, this.parameter, violation)); - this.violations.add(violation); } public ParameterValidationResult build() { - return new ParameterValidationResult( - this.parameter, this.argument, this.resolvableErrors, this.violations); + return new ParameterValidationResult(this.parameter, this.argument, this.resolvableErrors); } } @@ -485,8 +478,8 @@ public class MethodValidationAdapter { public ParameterErrors build() { validatorAdapter.get().processConstraintViolations(this.violations, this.errors); return new ParameterErrors( - this.parameter, this.argument, this.errors, this.violations, - this.container, this.containerIndex, this.containerKey); + this.parameter, this.argument, this.errors, this.container, + this.containerIndex, this.containerKey); } } @@ -532,4 +525,91 @@ public class MethodValidationAdapter { } } + + /** + * Default {@link MethodValidationResult} implementation with non-zero errors. + */ + private static class DefaultMethodValidationResult implements MethodValidationResult { + + private final Object target; + + private final Method method; + + private final List allValidationResults; + + private final boolean forReturnValue; + + + DefaultMethodValidationResult(Object target, Method method, List results) { + Assert.notEmpty(results, "'results' is required and must not be empty"); + Assert.notNull(target, "'target' is required"); + Assert.notNull(method, "Method is required"); + this.target = target; + this.method = method; + this.allValidationResults = results; + this.forReturnValue = (results.get(0).getMethodParameter().getParameterIndex() == -1); + } + + + @Override + public Object getTarget() { + return this.target; + } + + @Override + public Method getMethod() { + return this.method; + } + + @Override + public boolean isForReturnValue() { + return this.forReturnValue; + } + + @Override + public List getAllValidationResults() { + return this.allValidationResults; + } + + + @Override + public String toString() { + return getAllErrors().size() + " validation errors " + + "for " + (isForReturnValue() ? "return value" : "arguments") + " of " + + this.method.toGenericString(); + } + } + + + /** + * {@link MethodValidationResult} for when there are no errors. + */ + private static class EmptyMethodValidationResult implements MethodValidationResult { + + @Override + public Object getTarget() { + throw new UnsupportedOperationException(); + } + + @Override + public Method getMethod() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isForReturnValue() { + throw new UnsupportedOperationException(); + } + + @Override + public List getAllValidationResults() { + return Collections.emptyList(); + } + + @Override + public String toString() { + return "0 validation errors"; + } + } + } diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationException.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationException.java index a452e0f7724..470afa9d222 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationException.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationException.java @@ -17,131 +17,48 @@ package org.springframework.validation.beanvalidation; import java.lang.reflect.Method; -import java.util.Collections; import java.util.List; -import java.util.Set; - -import jakarta.validation.ConstraintViolation; -import jakarta.validation.ConstraintViolationException; import org.springframework.util.Assert; /** - * Extension of {@link ConstraintViolationException} that implements - * {@link MethodValidationResult} exposing an additional list of - * {@link ParameterValidationResult} that represents violations adapted to - * {@link org.springframework.context.MessageSourceResolvable} and grouped by - * method parameter. + * Exception that is a {@link MethodValidationResult}. * * @author Rossen Stoyanchev * @since 6.1 - * @see ParameterValidationResult - * @see ParameterErrors - * @see MethodValidationAdapter + * @see MethodValidator */ @SuppressWarnings("serial") -public class MethodValidationException extends ConstraintViolationException implements MethodValidationResult { - - private final Object target; - - private final Method method; - - private final List allValidationResults; - - private final boolean forReturnValue; - - - /** - * Package private constructor for {@link MethodValidationAdapter}. - */ - MethodValidationException( - Object target, Method method, boolean forReturnValue, - Set> violations, List results) { +public class MethodValidationException extends RuntimeException implements MethodValidationResult { - super(violations); + private final MethodValidationResult validationResult; - Assert.notNull(violations, "'violations' is required"); - Assert.notNull(results, "'results' is required"); - this.target = target; - this.method = method; - this.allValidationResults = results; - this.forReturnValue = forReturnValue; - } - - /** - * Private constructor copying from another {@code MethodValidationResult}. - */ - private MethodValidationException(MethodValidationResult other) { - this(other.getTarget(), other.getMethod(), other.isForReturnValue(), - other.getConstraintViolations(), other.getAllValidationResults()); + public MethodValidationException(MethodValidationResult validationResult) { + super(validationResult.toString()); + Assert.notNull(validationResult, "MethodValidationResult is required"); + this.validationResult = validationResult; } - // re-declare getConstraintViolations as NonNull - - @Override - public Set> getConstraintViolations() { - return super.getConstraintViolations(); - } - @Override public Object getTarget() { - return this.target; + return this.validationResult.getTarget(); } @Override public Method getMethod() { - return this.method; + return this.validationResult.getMethod(); } @Override public boolean isForReturnValue() { - return this.forReturnValue; + return this.validationResult.isForReturnValue(); } @Override public List getAllValidationResults() { - return this.allValidationResults; - } - - @Override - public List getValueResults() { - return this.allValidationResults.stream() - .filter(result -> !(result instanceof ParameterErrors)) - .toList(); - } - - @Override - public List getBeanResults() { - return this.allValidationResults.stream() - .filter(result -> result instanceof ParameterErrors) - .map(result -> (ParameterErrors) result) - .toList(); - } - - @Override - public String toString() { - return "MethodValidationResult (" + getConstraintViolations().size() + " violations) " + - "for " + this.method.toGenericString(); - } - - - /** - * Create an exception copying from the given result, or return the same - * instance if it is a {@code MethodValidationException} already. - */ - public static MethodValidationException forResult(MethodValidationResult result) { - return (result instanceof MethodValidationException ex ? ex : new MethodValidationException(result)); + return this.validationResult.getAllValidationResults(); } - /** - * Create an exception for validation without errors. - */ - public static MethodValidationException forEmptyResult(Object target, Method method, boolean forReturnValue) { - return new MethodValidationException( - target, method, forReturnValue, Collections.emptySet(), Collections.emptyList()); - } - - } 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 da2658e7859..5b687087b82 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 @@ -17,8 +17,11 @@ package org.springframework.validation.beanvalidation; import java.lang.reflect.Method; +import java.util.Set; import java.util.function.Supplier; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; import jakarta.validation.Validator; import jakarta.validation.ValidatorFactory; import org.aopalliance.intercept.MethodInterceptor; @@ -42,6 +45,10 @@ import org.springframework.validation.annotation.Validated; * *

E.g.: {@code public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)} * + *

In case of validation errors, the interceptor can raise + * {@link ConstraintViolationException}, or adapt the violations to + * {@link MethodValidationResult} and raise {@link MethodValidationException}. + * *

Validation groups can be specified through Spring's {@link Validated} annotation * at the type level of the containing target class, applying to all public service methods * of that class. By default, JSR-303 will validate against its default group only. @@ -49,20 +56,23 @@ import org.springframework.validation.annotation.Validated; *

As of Spring 5.0, this functionality requires a Bean Validation 1.1+ provider. * * @author Juergen Hoeller + * @author Rossen Stoyanchev * @since 3.1 * @see MethodValidationPostProcessor * @see jakarta.validation.executable.ExecutableValidator */ public class MethodValidationInterceptor implements MethodInterceptor { - private final MethodValidationAdapter delegate; + private final MethodValidationAdapter validationAdapter; + + private final boolean adaptViolations; /** * Create a new MethodValidationInterceptor using a default JSR-303 validator underneath. */ public MethodValidationInterceptor() { - this.delegate = new MethodValidationAdapter(); + this(new MethodValidationAdapter(), false); } /** @@ -70,7 +80,7 @@ public class MethodValidationInterceptor implements MethodInterceptor { * @param validatorFactory the JSR-303 ValidatorFactory to use */ public MethodValidationInterceptor(ValidatorFactory validatorFactory) { - this.delegate = new MethodValidationAdapter(validatorFactory); + this(new MethodValidationAdapter(validatorFactory), false); } /** @@ -78,7 +88,7 @@ public class MethodValidationInterceptor implements MethodInterceptor { * @param validator the JSR-303 Validator to use */ public MethodValidationInterceptor(Validator validator) { - this.delegate = new MethodValidationAdapter(validator); + this(new MethodValidationAdapter(validator), false); } /** @@ -88,7 +98,25 @@ public class MethodValidationInterceptor implements MethodInterceptor { * @since 6.0 */ public MethodValidationInterceptor(Supplier validator) { - this.delegate = new MethodValidationAdapter(validator); + this(validator, false); + } + + /** + * Create a new MethodValidationInterceptor for the supplied + * (potentially lazily initialized) Validator. + * @param validator a Supplier for the Validator to use + * @param adaptViolations whether to adapt {@link ConstraintViolation}s, and + * if {@code true}, raise {@link MethodValidationException}, of if + * {@code false} raise {@link ConstraintViolationException} instead + * @since 6.1 + */ + public MethodValidationInterceptor(Supplier validator, boolean adaptViolations) { + this(new MethodValidationAdapter(validator), adaptViolations); + } + + private MethodValidationInterceptor(MethodValidationAdapter validationAdapter, boolean adaptViolations) { + this.validationAdapter = validationAdapter; + this.adaptViolations = adaptViolations; } @@ -102,20 +130,31 @@ public class MethodValidationInterceptor implements MethodInterceptor { Object target = getTarget(invocation); Method method = invocation.getMethod(); + Object[] arguments = invocation.getArguments(); Class[] groups = determineValidationGroups(invocation); - MethodValidationResult result; + Set> violations; - result = this.delegate.validateMethodArguments(target, method, null, invocation.getArguments(), groups); - if (result.hasViolations()) { - throw MethodValidationException.forResult(result); + if (this.adaptViolations) { + this.validationAdapter.applyArgumentValidation(target, method, null, arguments, groups); + } + else { + violations = this.validationAdapter.invokeValidatorForArguments(target, method, arguments, groups); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } } Object returnValue = invocation.proceed(); - result = this.delegate.validateMethodReturnValue(target, method, null, returnValue, groups); - if (result.hasViolations()) { - throw MethodValidationException.forResult(result); + if (this.adaptViolations) { + this.validationAdapter.applyReturnValueValidation(target, method, null, arguments, groups); + } + else { + violations = this.validationAdapter.invokeValidatorForReturnValue(target, method, returnValue, groups); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } } return returnValue; @@ -162,8 +201,7 @@ public class MethodValidationInterceptor implements MethodInterceptor { */ protected Class[] determineValidationGroups(MethodInvocation invocation) { Object target = getTarget(invocation); - Method method = invocation.getMethod(); - return MethodValidationAdapter.determineValidationGroups(target, method); + return this.validationAdapter.determineValidationGroups(target, invocation.getMethod()); } } diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationPostProcessor.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationPostProcessor.java index e0b14b865a4..4ba66ff5125 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ package org.springframework.validation.beanvalidation; import java.lang.annotation.Annotation; import java.util.function.Supplier; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; import jakarta.validation.Validation; import jakarta.validation.Validator; import jakarta.validation.ValidatorFactory; @@ -47,6 +49,10 @@ import org.springframework.validation.annotation.Validated; * public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2) * * + *

In case of validation errors, the interceptor can raise + * {@link ConstraintViolationException}, or adapt the violations to + * {@link MethodValidationResult} and raise {@link MethodValidationException}. + * *

Target classes with such annotated methods need to be annotated with Spring's * {@link Validated} annotation at the type level, for their methods to be searched for * inline constraint annotations. Validation groups can be specified through {@code @Validated} @@ -68,6 +74,8 @@ public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvis private Supplier validator = SingletonSupplier.of(() -> Validation.buildDefaultValidatorFactory().getValidator()); + private boolean adaptConstraintViolations; + /** * Set the 'validated' annotation type. @@ -109,6 +117,18 @@ public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvis this.validator = validatorProvider::getObject; } + /** + * Whether to adapt {@link ConstraintViolation}s to {@link MethodValidationResult}. + *

By default {@code false} in which case + * {@link jakarta.validation.ConstraintViolationException} is raised in case of + * violations. When set to {@code true}, {@link MethodValidationException} + * is raised instead with the method validation results. + * @since 6.1 + */ + public void setAdaptConstraintViolations(boolean adaptViolations) { + this.adaptConstraintViolations = adaptViolations; + } + @Override public void afterPropertiesSet() { @@ -125,7 +145,7 @@ public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvis * @since 6.0 */ protected Advice createMethodValidationAdvice(Supplier validator) { - return new MethodValidationInterceptor(validator); + return new MethodValidationInterceptor(validator, this.adaptConstraintViolations); } } diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationResult.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationResult.java index 33cc7b0433f..316dbc0a424 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationResult.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationResult.java @@ -18,22 +18,16 @@ package org.springframework.validation.beanvalidation; import java.lang.reflect.Method; import java.util.List; -import java.util.Set; -import jakarta.validation.ConstraintViolation; +import org.springframework.context.MessageSourceResolvable; +import org.springframework.validation.Errors; /** - * Container for method validation results where underlying - * {@link ConstraintViolation violations} have been adapted to - * {@link ParameterValidationResult} each containing a list of - * {@link org.springframework.context.MessageSourceResolvable} grouped by method - * parameter. - * - *

For {@link jakarta.validation.Valid @Valid}-annotated, Object method - * parameters or return types with cascaded violations, the {@link ParameterErrors} - * subclass of {@link ParameterValidationResult} implements - * {@link org.springframework.validation.Errors} and exposes - * {@link org.springframework.validation.FieldError field errors}. + * Container for method validation results with validation errors from the + * underlying library adapted to {@link MessageSourceResolvable}s and grouped + * by method parameter as {@link ParameterValidationResult}. For method parameters + * with nested validation errors, the validation result is of type + * {@link ParameterErrors} and implements {@link Errors}. * * @author Rossen Stoyanchev * @since 6.1 @@ -58,43 +52,53 @@ public interface MethodValidationResult { boolean isForReturnValue(); /** - * Whether the result contains any {@link ConstraintViolation}s. + * Whether the result contains any validation errors. */ - default boolean hasViolations() { - return !getConstraintViolations().isEmpty(); + default boolean hasErrors() { + return !getAllValidationResults().isEmpty(); } /** - * Returns the set of constraint violations reported during a validation. - * @return the {@code Set} of {@link ConstraintViolation}s, or an empty Set + * Return a single list with all errors from all validation results. + * @see #getAllValidationResults() + * @see ParameterValidationResult#getResolvableErrors() */ - Set> getConstraintViolations(); + default List getAllErrors() { + return getAllValidationResults().stream() + .flatMap(result -> result.getResolvableErrors().stream()) + .toList(); + } /** - * Return all validation results. This includes method parameters with - * constraints declared on them, as well as - * {@link jakarta.validation.Valid @Valid} method parameters with - * cascaded constraints. + * Return all validation results. This includes both method parameters with + * errors directly on them, and Object method parameters with nested errors + * on their fields and properties. * @see #getValueResults() * @see #getBeanResults() */ List getAllValidationResults(); /** - * Return only validation results for method parameters with constraints - * declared directly on them. This excludes - * {@link jakarta.validation.Valid @Valid} method parameters with cascaded - * constraints. - * @see #getAllValidationResults() + * Return only validation results for method parameters with errors directly + * on them. This does not include Object method parameters with nested + * errors on their fields and properties. */ - List getValueResults(); + default List getValueResults() { + return getAllValidationResults().stream() + .filter(result -> !(result instanceof ParameterErrors)) + .toList(); + } /** - * Return only validation results for {@link jakarta.validation.Valid @Valid} - * method parameters with cascaded constraints. This excludes method - * parameters with constraints declared directly on them. - * @see #getAllValidationResults() + * Return only validation results for Object method parameters with nested + * errors on their fields and properties. This excludes method parameters + * with errors directly on them. */ - List getBeanResults(); + default List getBeanResults() { + return getAllValidationResults().stream() + .filter(result -> result instanceof ParameterErrors) + .map(result -> (ParameterErrors) result) + .toList(); + } } 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 d549422a5e2..1922383eded 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 @@ -22,44 +22,78 @@ import org.springframework.core.MethodParameter; import org.springframework.lang.Nullable; /** - * Contract to apply method validation without directly using - * {@link MethodValidationAdapter}. For use in components where Jakarta Bean - * Validation is an optional dependency and may or may not be present on the - * classpath. If that's not a concern, use {@code MethodValidationAdapter} - * directly. + * Contract to apply method validation and handle the results. + * Exposes methods that return {@link MethodValidationResult}, and methods that + * handle the results, by default raising {@link MethodValidationException}. * * @author Rossen Stoyanchev * @since 6.1 - * @see DefaultMethodValidator */ public interface MethodValidator { /** - * Use this method determine the validation groups to pass into - * {@link #validateArguments(Object, Method, MethodParameter[], Object[], Class[])} and - * {@link #validateReturnValue(Object, Method, MethodParameter, Object, Class[])}. + * Use this method to determine the validation groups. * @param target the target Object * @param method the target method * @return the applicable validation groups as a {@code Class} array - * @see MethodValidationAdapter#determineValidationGroups(Object, Method) */ Class[] determineValidationGroups(Object target, Method method); /** - * Validate the given method arguments and return the result of validation. + * Validate the given method arguments and handle the result. * @param target the target Object * @param method the target method * @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). + * @param groups validation groups via {@link #determineValidationGroups} + * @throws MethodValidationException raised by default in case of validation errors. + * Implementations may provide alternative handling, possibly not raise an exception + * but for example inject errors into the method, or raise a different exception, + * one that also implements {@link MethodValidationResult}. */ - void validateArguments( - Object target, Method method, @Nullable MethodParameter[] parameters, Object[] arguments, - Class[] groups); + default void applyArgumentValidation( + Object target, Method method, @Nullable MethodParameter[] parameters, + Object[] arguments, Class[] groups) { + + MethodValidationResult result = validateArguments(target, method, parameters, arguments, groups); + if (result.hasErrors()) { + throw new MethodValidationException(result); + } + } + + /** + * Validate the given method arguments and return validation results. + * @param target the target Object + * @param method the target method + * @param parameters the parameters, if already created and available + * @param arguments the candidate argument values to validate + * @param groups validation groups from {@link #determineValidationGroups} + * @return the result of validation + */ + MethodValidationResult validateArguments( + Object target, Method method, @Nullable MethodParameter[] parameters, + Object[] arguments, Class[] groups); + + /** + * Validate the given return value and handle the results. + * @param target the target Object + * @param method the target method + * @param returnType the return parameter, if already created and available + * @param returnValue the return value to validate + * @param groups validation groups from {@link #determineValidationGroups} + * @throws MethodValidationException raised by default in case of validation errors. + * Implementations may provide alternative handling, or raise a different exception, + * one that also implements {@link MethodValidationResult}. + */ + default void applyReturnValueValidation( + Object target, Method method, @Nullable MethodParameter returnType, + @Nullable Object returnValue, Class[] groups) { + + MethodValidationResult result = validateReturnValue(target, method, returnType, returnValue, groups); + if (result.hasErrors()) { + throw new MethodValidationException(result); + } + } /** * Validate the given return value and return the result of validation. @@ -67,12 +101,11 @@ public interface MethodValidator { * @param method 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 + * @param groups validation groups from {@link #determineValidationGroups} + * @return the result of validation */ - void validateReturnValue( - Object target, Method method, @Nullable MethodParameter returnType, @Nullable Object returnValue, - Class[] groups); + MethodValidationResult validateReturnValue( + Object target, Method method, @Nullable MethodParameter returnType, + @Nullable Object returnValue, Class[] groups); } diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/ParameterErrors.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/ParameterErrors.java index cae8ac5e713..5a3d91a90a9 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/ParameterErrors.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/ParameterErrors.java @@ -16,11 +16,8 @@ package org.springframework.validation.beanvalidation; -import java.util.Collection; import java.util.List; -import jakarta.validation.ConstraintViolation; - import org.springframework.core.MethodParameter; import org.springframework.lang.Nullable; import org.springframework.validation.Errors; @@ -28,20 +25,18 @@ import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; /** - * Extension of {@link ParameterValidationResult} that's created for Object - * method arguments or return values with cascaded violations on their properties. - * Such method parameters are annotated with {@link jakarta.validation.Valid @Valid}, - * or in the case of return values, the annotation is on the method. + * Extension of {@link ParameterValidationResult} created for Object method + * parameters or return values with nested errors on their properties. * - *

In addition to the (generic) {@link #getResolvableErrors() - * MessageSourceResolvable errors} from the base class, this subclass implements - * {@link Errors} to expose convenient access to the same as {@link FieldError}s. + *

The base class method {@link #getResolvableErrors()} returns + * {@link Errors#getAllErrors()}, but this subclass provides access to the same + * as {@link FieldError}s. * - *

When {@code @Valid} is declared on a {@link List} or {@link java.util.Map} - * parameter, a separate {@link ParameterErrors} is created for each list or map - * value for which there are constraint violations. In such cases, the - * {@link #getContainer()} is the list or map, while {@link #getContainerIndex()} - * and {@link #getContainerKey()} reflect the index or key of the value. + *

When the method parameter is a {@link List} or {@link java.util.Map}, + * a separate {@link ParameterErrors} is created for each list or map value for + * which there are validation errors. In such cases, the {@link #getContainer()} + * method returns the list or map, while {@link #getContainerIndex()} + * and {@link #getContainerKey()} return the value index or key. * * @author Rossen Stoyanchev * @since 6.1 @@ -65,11 +60,9 @@ public class ParameterErrors extends ParameterValidationResult implements Errors */ public ParameterErrors( MethodParameter parameter, @Nullable Object argument, Errors errors, - Collection> violations, @Nullable Object container, @Nullable Integer index, @Nullable Object key) { - super(parameter, argument, errors.getAllErrors(), violations); - + super(parameter, argument, errors.getAllErrors()); this.errors = errors; this.container = container; this.containerIndex = index; diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/ParameterValidationResult.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/ParameterValidationResult.java index d71dbdbf5d0..4091f0aec41 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/ParameterValidationResult.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/ParameterValidationResult.java @@ -19,8 +19,6 @@ package org.springframework.validation.beanvalidation; import java.util.Collection; import java.util.List; -import jakarta.validation.ConstraintViolation; - import org.springframework.context.MessageSourceResolvable; import org.springframework.core.MethodParameter; import org.springframework.lang.Nullable; @@ -28,15 +26,13 @@ import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** - * Store and expose the results of method validation via - * {@link jakarta.validation.Validator} for a specific method parameter. + * Store and expose the results of method validation for a method parameter. *

    - *
  • For a constraints directly on a method parameter, each - * {@link ConstraintViolation} is adapted to {@link MessageSourceResolvable}. - *
  • For cascaded constraints via {@link jakarta.validation.Validator @Valid} - * on a bean method parameter, {@link SpringValidatorAdapter} is used to initialize - * an {@link org.springframework.validation.Errors} with field errors, and create - * the {@link ParameterErrors} sub-class. + *
  • Validation errors directly on method parameter values are exposed as a + * list of {@link MessageSourceResolvable}s. + *
  • Nested validation errors on an Object method parameter are exposed as + * {@link org.springframework.validation.Errors} by the subclass + * {@link ParameterErrors}. *
* * @author Rossen Stoyanchev @@ -51,24 +47,18 @@ public class ParameterValidationResult { private final List resolvableErrors; - private final List> violations; - /** * Create a {@code ParameterValidationResult}. */ public ParameterValidationResult( - MethodParameter methodParameter, @Nullable Object argument, - Collection resolvableErrors, - Collection> violations) { - - Assert.notNull(methodParameter, "MethodParameter is required"); - Assert.notEmpty(resolvableErrors, "`resolvableErrors` must not be empty"); - Assert.notEmpty(violations, "'violations' must not be empty"); - this.methodParameter = methodParameter; - this.argument = argument; - this.resolvableErrors = List.copyOf(resolvableErrors); - this.violations = List.copyOf(violations); + MethodParameter param, @Nullable Object arg, Collection errors) { + + Assert.notNull(param, "MethodParameter is required"); + Assert.notEmpty(errors, "`resolvableErrors` must not be empty"); + this.methodParameter = param; + this.argument = arg; + this.resolvableErrors = List.copyOf(errors); } @@ -89,7 +79,7 @@ public class ParameterValidationResult { /** * List of {@link MessageSourceResolvable} representations adapted from the - * underlying {@link #getViolations() violations}. + * validation errors of the validation library. *
    *
  • For a constraints directly on a method parameter, error codes are * based on the names of the constraint annotation, the object, the method, @@ -110,14 +100,6 @@ public class ParameterValidationResult { return this.resolvableErrors; } - /** - * The violations associated with the method parameter, in the same order - * as {@link #getResolvableErrors()}. - */ - public List> getViolations() { - return this.violations; - } - @Override public boolean equals(@Nullable Object other) { @@ -129,8 +111,7 @@ public class ParameterValidationResult { } ParameterValidationResult otherResult = (ParameterValidationResult) other; return (getMethodParameter().equals(otherResult.getMethodParameter()) && - ObjectUtils.nullSafeEquals(getArgument(), otherResult.getArgument()) && - getViolations().equals(otherResult.getViolations())); + ObjectUtils.nullSafeEquals(getArgument(), otherResult.getArgument())); } @Override @@ -138,7 +119,6 @@ public class ParameterValidationResult { int hashCode = super.hashCode(); hashCode = 29 * hashCode + getMethodParameter().hashCode(); hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(getArgument()); - hashCode = 29 * hashCode + (getViolations().hashCode()); return hashCode; } 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 a9775e97779..f62eec9db46 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 @@ -69,9 +69,8 @@ public class MethodValidationAdapterTests { MyService target = new MyService(); Method method = getMethod(target, "addStudent"); - validateArguments(target, method, new Object[] {faustino1234, cayetana6789, 3}, ex -> { + testArgs(target, method, new Object[] {faustino1234, cayetana6789, 3}, ex -> { - assertThat(ex.getConstraintViolations()).hasSize(3); assertThat(ex.getAllValidationResults()).hasSize(3); assertBeanResult(ex.getBeanResults().get(0), 0, "student", faustino1234, List.of(""" @@ -104,9 +103,8 @@ public class MethodValidationAdapterTests { this.validationAdapter.setBindingResultNameResolver((parameter, value) -> "studentToAdd"); - validateArguments(target, method, new Object[] {faustino1234, new Person("Joe"), 1}, ex -> { + testArgs(target, method, new Object[] {faustino1234, new Person("Joe"), 1}, ex -> { - assertThat(ex.getConstraintViolations()).hasSize(1); assertThat(ex.getAllValidationResults()).hasSize(1); assertBeanResult(ex.getBeanResults().get(0), 0, "studentToAdd", faustino1234, List.of(""" @@ -122,9 +120,8 @@ public class MethodValidationAdapterTests { void validateReturnValue() { MyService target = new MyService(); - validateReturnValue(target, getMethod(target, "getIntValue"), 4, ex -> { + testReturnValue(target, getMethod(target, "getIntValue"), 4, ex -> { - assertThat(ex.getConstraintViolations()).hasSize(1); assertThat(ex.getAllValidationResults()).hasSize(1); assertValueResult(ex.getValueResults().get(0), -1, 4, List.of(""" @@ -140,9 +137,8 @@ public class MethodValidationAdapterTests { void validateReturnValueBean() { MyService target = new MyService(); - validateReturnValue(target, getMethod(target, "getPerson"), faustino1234, ex -> { + testReturnValue(target, getMethod(target, "getPerson"), faustino1234, ex -> { - assertThat(ex.getConstraintViolations()).hasSize(1); assertThat(ex.getAllValidationResults()).hasSize(1); assertBeanResult(ex.getBeanResults().get(0), -1, "person", faustino1234, List.of(""" @@ -159,9 +155,8 @@ public class MethodValidationAdapterTests { MyService target = new MyService(); Method method = getMethod(target, "addPeople"); - validateArguments(target, method, new Object[] {List.of(faustino1234, cayetana6789)}, ex -> { + testArgs(target, method, new Object[] {List.of(faustino1234, cayetana6789)}, ex -> { - assertThat(ex.getConstraintViolations()).hasSize(2); assertThat(ex.getAllValidationResults()).hasSize(2); int paramIndex = 0; @@ -184,18 +179,12 @@ public class MethodValidationAdapterTests { }); } - private void validateArguments( - Object target, Method method, Object[] arguments, Consumer assertions) { - - assertions.accept( - this.validationAdapter.validateMethodArguments(target, method, null, arguments, new Class[0])); + private void testArgs(Object target, Method method, Object[] args, Consumer consumer) { + consumer.accept(this.validationAdapter.validateArguments(target, method, null, args, new Class[0])); } - private void validateReturnValue( - Object target, Method method, @Nullable Object returnValue, Consumer assertions) { - - assertions.accept( - this.validationAdapter.validateMethodReturnValue(target, method, null, returnValue, new Class[0])); + private void testReturnValue(Object target, Method method, @Nullable Object value, Consumer consumer) { + consumer.accept(this.validationAdapter.validateReturnValue(target, method, null, value, new Class[0])); } private static void assertBeanResult( diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.java b/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.java index 41c5451fe17..3e7a1265fae 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.java @@ -16,6 +16,8 @@ package org.springframework.web.method.annotation; +import java.lang.reflect.Method; + import jakarta.validation.Validator; import org.springframework.core.Conventions; @@ -24,7 +26,6 @@ import org.springframework.core.ParameterNameDiscoverer; import org.springframework.lang.Nullable; import org.springframework.validation.BindingResult; import org.springframework.validation.MessageCodesResolver; -import org.springframework.validation.beanvalidation.DefaultMethodValidator; import org.springframework.validation.beanvalidation.MethodValidationAdapter; import org.springframework.validation.beanvalidation.MethodValidationException; import org.springframework.validation.beanvalidation.MethodValidationResult; @@ -37,27 +38,41 @@ import org.springframework.web.bind.support.WebBindingInitializer; /** * {@link org.springframework.validation.beanvalidation.MethodValidator} for - * use with {@code @RequestMapping} methods. Helps to determine object names - * and populates {@link BindingResult} method arguments with errors from - * {@link MethodValidationResult#getBeanResults() beanResults}. + * {@code @RequestMapping} methods. + * + *

    Handles validation results by populating {@link BindingResult} method + * arguments with errors from {@link MethodValidationResult#getBeanResults() + * beanResults}. Also, helps to determine parameter names for + * {@code @ModelAttribute} and {@code @RequestBody} parameters. * * @author Rossen Stoyanchev * @since 6.1 */ -public final class HandlerMethodValidator extends DefaultMethodValidator { +public final class HandlerMethodValidator implements MethodValidator { + + private final MethodValidationAdapter validationAdapter; - private HandlerMethodValidator(MethodValidationAdapter adapter) { - super(adapter); + + private HandlerMethodValidator(MethodValidationAdapter validationAdapter) { + this.validationAdapter = validationAdapter; } @Override - protected void handleArgumentsResult( - Object[] arguments, Class[] groups, MethodValidationResult result) { + public Class[] determineValidationGroups(Object target, Method method) { + return this.validationAdapter.determineValidationGroups(target, method); + } - if (result.getConstraintViolations().isEmpty()) { + @Override + public void applyArgumentValidation( + Object target, Method method, @Nullable MethodParameter[] parameters, + Object[] arguments, Class[] groups) { + + MethodValidationResult result = validateArguments(target, method, parameters, arguments, groups); + if (!result.hasErrors()) { return; } + if (!result.getBeanResults().isEmpty()) { int bindingResultCount = 0; for (ParameterErrors errors : result.getBeanResults()) { @@ -75,12 +90,37 @@ public final class HandlerMethodValidator extends DefaultMethodValidator { return; } } - if (result.hasViolations()) { - throw MethodValidationException.forResult(result); + + throw new MethodValidationException(result); + } + + @Override + public MethodValidationResult validateArguments( + Object target, Method method, @Nullable MethodParameter[] parameters, + Object[] arguments, Class[] groups) { + + return this.validationAdapter.validateArguments(target, method, parameters, arguments, groups); + } + + @Override + public void applyReturnValueValidation( + Object target, Method method, @Nullable MethodParameter returnType, + @Nullable Object returnValue, Class[] groups) { + + MethodValidationResult result = validateReturnValue(target, method, returnType, returnValue, groups); + if (result.hasErrors()) { + throw new MethodValidationException(result); } } - private String determineObjectName(MethodParameter param, @Nullable Object argument) { + @Override + public MethodValidationResult validateReturnValue(Object target, Method method, + @Nullable MethodParameter returnType, @Nullable Object returnValue, Class[] groups) { + + return this.validationAdapter.validateReturnValue(target, method, returnType, returnValue, groups); + } + + private static String determineObjectName(MethodParameter param, @Nullable Object argument) { if (param.hasParameterAnnotation(RequestBody.class) || param.hasParameterAnnotation(RequestPart.class)) { return Conventions.getVariableNameForParameter(param); } @@ -112,7 +152,7 @@ public final class HandlerMethodValidator extends DefaultMethodValidator { adapter.setMessageCodesResolver(codesResolver); } HandlerMethodValidator methodValidator = new HandlerMethodValidator(adapter); - adapter.setBindingResultNameResolver(methodValidator::determineObjectName); + adapter.setBindingResultNameResolver(HandlerMethodValidator::determineObjectName); return methodValidator; } } 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 24dc1012fa6..21e4db3b6cb 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 @@ -174,14 +174,14 @@ public class InvocableHandlerMethod extends HandlerMethod { Class[] groups = getValidationGroups(); if (shouldValidateArguments() && this.methodValidator != null) { - this.methodValidator.validateArguments( + this.methodValidator.applyArgumentValidation( getBean(), getBridgedMethod(), getMethodParameters(), args, groups); } Object returnValue = doInvoke(args); if (shouldValidateReturnValue() && this.methodValidator != null) { - this.methodValidator.validateReturnValue( + this.methodValidator.applyReturnValueValidation( getBean(), getBridgedMethod(), getReturnType(), returnValue, groups); } 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 3547fd95d03..e730a07ad9b 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 @@ -159,7 +159,7 @@ public class InvocableHandlerMethod extends HandlerMethod { return getMethodArgumentValues(exchange, bindingContext, providedArgs).flatMap(args -> { Class[] groups = getValidationGroups(); if (shouldValidateArguments() && this.methodValidator != null) { - this.methodValidator.validateArguments( + this.methodValidator.applyArgumentValidation( getBean(), getBridgedMethod(), getMethodParameters(), args, groups); } Object value; diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MethodValidationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MethodValidationTests.java index 5b035cf23b2..a2649c6e527 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MethodValidationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MethodValidationTests.java @@ -207,7 +207,6 @@ public class MethodValidationTests { assertThat(this.jakartaValidator.getValidationCount()).isEqualTo(1); assertThat(this.jakartaValidator.getMethodValidationCount()).isEqualTo(1); - assertThat(ex.getConstraintViolations()).hasSize(2); assertThat(ex.getAllValidationResults()).hasSize(2); assertBeanResult(ex.getBeanResults().get(0), "student", Collections.singletonList( diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MethodValidationTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MethodValidationTests.java index 719ea21ae48..fb27dfad8b2 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MethodValidationTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MethodValidationTests.java @@ -51,6 +51,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.context.support.GenericWebApplicationContext; import org.springframework.web.method.HandlerMethod; +import org.springframework.web.method.annotation.HandlerMethodValidationException; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.testfixture.method.ResolvableMethod; import org.springframework.web.testfixture.servlet.MockHttpServletRequest; @@ -170,7 +171,6 @@ public class MethodValidationTests { assertThat(this.jakartaValidator.getValidationCount()).isEqualTo(1); assertThat(this.jakartaValidator.getMethodValidationCount()).isEqualTo(1); - assertThat(ex.getConstraintViolations()).hasSize(2); assertThat(ex.getAllValidationResults()).hasSize(2); assertBeanResult(ex.getBeanResults().get(0), "student", Collections.singletonList(