diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationDelegate.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationDelegate.java
new file mode 100644
index 0000000000..5509d23eab
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationDelegate.java
@@ -0,0 +1,161 @@
+/*
+ * 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 java.util.Set;
+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;
+import jakarta.validation.executable.ExecutableValidator;
+
+import org.springframework.aop.framework.AopProxyUtils;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.core.BridgeMethodResolver;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.lang.Nullable;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.function.SingletonSupplier;
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * Helper class to apply method-level validation on annotated methods via
+ * {@link jakarta.validation.Valid}.
+ *
+ *
Used by {@link MethodValidationInterceptor}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 6.1.0
+ */
+public class MethodValidationDelegate {
+
+ private final Supplier validator;
+
+
+ /**
+ * Create an instance using a default JSR-303 validator underneath.
+ */
+ public MethodValidationDelegate() {
+ this.validator = SingletonSupplier.of(() -> Validation.buildDefaultValidatorFactory().getValidator());
+ }
+
+ /**
+ * Create an instance using the given JSR-303 ValidatorFactory.
+ * @param validatorFactory the JSR-303 ValidatorFactory to use
+ */
+ public MethodValidationDelegate(ValidatorFactory validatorFactory) {
+ this.validator = SingletonSupplier.of(validatorFactory::getValidator);
+ }
+
+ /**
+ * Create an instance using the given JSR-303 Validator.
+ * @param validator the JSR-303 Validator to use
+ */
+ public MethodValidationDelegate(Validator validator) {
+ this.validator = () -> validator;
+ }
+
+ /**
+ * Create an instance for the supplied (potentially lazily initialized) Validator.
+ * @param validator a Supplier for the Validator to use
+ */
+ public MethodValidationDelegate(Supplier validator) {
+ this.validator = validator;
+ }
+
+
+ /**
+ * Use this method determine the validation groups to pass into
+ * {@link #validateMethodArguments(Object, Method, Object[], Class[])} and
+ * {@link #validateMethodReturnValue(Object, Method, 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
+ * 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 Class>[] determineValidationGroups(Object target, Method method) {
+ Validated validatedAnn = AnnotationUtils.findAnnotation(method, Validated.class);
+ if (validatedAnn == null) {
+ if (AopUtils.isAopProxy(target)) {
+ for (Class> type : AopProxyUtils.proxiedUserInterfaces(target)) {
+ validatedAnn = AnnotationUtils.findAnnotation(type, Validated.class);
+ if (validatedAnn != null) {
+ break;
+ }
+ }
+ }
+ else {
+ validatedAnn = AnnotationUtils.findAnnotation(target.getClass(), Validated.class);
+ }
+ }
+ return (validatedAnn != null ? validatedAnn.value() : new Class>[0]);
+ }
+
+ /**
+ * Validate the given method arguments and raise {@link ConstraintViolation}
+ * in case of any errors.
+ * @param target the target Object
+ * @param method the target method
+ * @param arguments candidate arguments for a method invocation
+ * @param groups groups for validation determined via
+ * {@link #determineValidationGroups(Object, Method)}
+ */
+ public void validateMethodArguments(Object target, Method method, Object[] arguments, Class>[] groups) {
+ ExecutableValidator execVal = this.validator.get().forExecutables();
+ Set> result;
+ try {
+ result = 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);
+ }
+ if (!result.isEmpty()) {
+ throw new ConstraintViolationException(result);
+ }
+ }
+
+ /**
+ * Validate the given return value and raise {@link ConstraintViolation}
+ * in case of any errors.
+ * @param target the target Object
+ * @param method the target method
+ * @param returnValue value returned from invoking the target method
+ * @param groups groups for validation determined via
+ * {@link #determineValidationGroups(Object, Method)}
+ */
+ public void validateMethodReturnValue(
+ Object target, Method method, @Nullable Object returnValue, Class>[] groups) {
+
+ ExecutableValidator execVal = this.validator.get().forExecutables();
+ Set> result = execVal.validateReturnValue(target, method, returnValue, groups);
+ if (!result.isEmpty()) {
+ throw new ConstraintViolationException(result);
+ }
+ }
+
+}
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 059abb5d0d..b6dc9fbbe8 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,38 +17,28 @@
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.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
-import jakarta.validation.executable.ExecutableValidator;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.ProxyMethodInvocation;
-import org.springframework.aop.framework.AopProxyUtils;
-import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.SmartFactoryBean;
-import org.springframework.core.BridgeMethodResolver;
-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.annotation.Validated;
/**
* An AOP Alliance {@link MethodInterceptor} implementation that delegates to a
* JSR-303 provider for performing method-level validation on annotated methods.
*
- * Applicable methods have JSR-303 constraint annotations on their parameters
- * and/or on their return value (in the latter case specified at the method level,
- * typically as inline annotation).
+ *
Applicable methods have {@link jakarta.validation.Constraint} annotations on
+ * their parameters and/or on their return value (in the latter case specified at
+ * the method level, typically as inline annotation).
*
*
E.g.: {@code public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)}
*
@@ -65,14 +55,14 @@ import org.springframework.validation.annotation.Validated;
*/
public class MethodValidationInterceptor implements MethodInterceptor {
- private final Supplier validator;
+ private final MethodValidationDelegate delegate;
/**
* Create a new MethodValidationInterceptor using a default JSR-303 validator underneath.
*/
public MethodValidationInterceptor() {
- this.validator = SingletonSupplier.of(() -> Validation.buildDefaultValidatorFactory().getValidator());
+ this.delegate = new MethodValidationDelegate();
}
/**
@@ -80,7 +70,7 @@ public class MethodValidationInterceptor implements MethodInterceptor {
* @param validatorFactory the JSR-303 ValidatorFactory to use
*/
public MethodValidationInterceptor(ValidatorFactory validatorFactory) {
- this.validator = SingletonSupplier.of(validatorFactory::getValidator);
+ this.delegate = new MethodValidationDelegate(validatorFactory);
}
/**
@@ -88,7 +78,7 @@ public class MethodValidationInterceptor implements MethodInterceptor {
* @param validator the JSR-303 Validator to use
*/
public MethodValidationInterceptor(Validator validator) {
- this.validator = () -> validator;
+ this.delegate = new MethodValidationDelegate(validator);
}
/**
@@ -98,7 +88,7 @@ public class MethodValidationInterceptor implements MethodInterceptor {
* @since 6.0
*/
public MethodValidationInterceptor(Supplier validator) {
- this.validator = validator;
+ this.delegate = new MethodValidationDelegate(validator);
}
@@ -110,42 +100,25 @@ public class MethodValidationInterceptor implements MethodInterceptor {
return invocation.proceed();
}
+ Object target = getTarget(invocation);
+ Method method = invocation.getMethod();
Class>[] groups = determineValidationGroups(invocation);
+ this.delegate.validateMethodArguments(target, method, invocation.getArguments(), groups);
- // Standard Bean Validation 1.1 API
- ExecutableValidator execVal = this.validator.get().forExecutables();
- Method methodToValidate = invocation.getMethod();
- Set> result;
+ Object returnValue = invocation.proceed();
+ this.delegate.validateMethodReturnValue(target, method, returnValue, groups);
+ return returnValue;
+ }
+
+ private static Object getTarget(MethodInvocation invocation) {
Object target = invocation.getThis();
if (target == null && invocation instanceof ProxyMethodInvocation methodInvocation) {
// Allow validation for AOP proxy without a target
target = methodInvocation.getProxy();
}
Assert.state(target != null, "Target must not be null");
-
- try {
- result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), 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...
- methodToValidate = BridgeMethodResolver.findBridgedMethod(
- ClassUtils.getMostSpecificMethod(invocation.getMethod(), target.getClass()));
- result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups);
- }
- if (!result.isEmpty()) {
- throw new ConstraintViolationException(result);
- }
-
- Object returnValue = invocation.proceed();
-
- result = execVal.validateReturnValue(target, methodToValidate, returnValue, groups);
- if (!result.isEmpty()) {
- throw new ConstraintViolationException(result);
- }
-
- return returnValue;
+ return target;
}
private boolean isFactoryBeanMetadataMethod(Method method) {
@@ -178,25 +151,9 @@ public class MethodValidationInterceptor implements MethodInterceptor {
* @return the applicable validation groups as a Class array
*/
protected Class>[] determineValidationGroups(MethodInvocation invocation) {
- Validated validatedAnn = AnnotationUtils.findAnnotation(invocation.getMethod(), Validated.class);
- if (validatedAnn == null) {
- Object target = invocation.getThis();
- if (target != null) {
- validatedAnn = AnnotationUtils.findAnnotation(target.getClass(), Validated.class);
- }
- else if (invocation instanceof ProxyMethodInvocation methodInvocation) {
- Object proxy = methodInvocation.getProxy();
- if (AopUtils.isAopProxy(proxy)) {
- for (Class> type : AopProxyUtils.proxiedUserInterfaces(proxy)) {
- validatedAnn = AnnotationUtils.findAnnotation(type, Validated.class);
- if (validatedAnn != null) {
- break;
- }
- }
- }
- }
- }
- return (validatedAnn != null ? validatedAnn.value() : new Class>[0]);
+ Object target = getTarget(invocation);
+ Method method = invocation.getMethod();
+ return this.delegate.determineValidationGroups(target, method);
}
}