Reactive support in MethodValidationInterceptor
Closes gh-20781
This commit is contained in:
@@ -140,6 +140,13 @@ public class MethodValidationAdapter implements MethodValidator {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the {@link SpringValidatorAdapter} configured for use.
|
||||
*/
|
||||
public Supplier<SpringValidatorAdapter> getSpringValidatorAdapter() {
|
||||
return this.validatorAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the strategy to use to determine message codes for violations.
|
||||
* <p>Default is a DefaultMessageCodesResolver.
|
||||
|
||||
@@ -17,25 +17,39 @@
|
||||
package org.springframework.validation.beanvalidation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.Validator;
|
||||
import jakarta.validation.ValidatorFactory;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.aop.ProxyMethodInvocation;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.SmartFactoryBean;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ReactiveAdapter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.validation.method.MethodValidationException;
|
||||
import org.springframework.validation.method.MethodValidationResult;
|
||||
import org.springframework.validation.method.ParameterErrors;
|
||||
import org.springframework.validation.method.ParameterValidationResult;
|
||||
|
||||
/**
|
||||
* An AOP Alliance {@link MethodInterceptor} implementation that delegates to a
|
||||
@@ -65,6 +79,10 @@ import org.springframework.validation.method.MethodValidationResult;
|
||||
*/
|
||||
public class MethodValidationInterceptor implements MethodInterceptor {
|
||||
|
||||
private static final boolean REACTOR_PRESENT =
|
||||
ClassUtils.isPresent("reactor.core.publisher.Mono", MethodValidationInterceptor.class.getClassLoader());
|
||||
|
||||
|
||||
private final MethodValidationAdapter validationAdapter;
|
||||
|
||||
private final boolean adaptViolations;
|
||||
@@ -135,6 +153,12 @@ public class MethodValidationInterceptor implements MethodInterceptor {
|
||||
Object[] arguments = invocation.getArguments();
|
||||
Class<?>[] groups = determineValidationGroups(invocation);
|
||||
|
||||
if (REACTOR_PRESENT) {
|
||||
arguments = ReactorValidationHelper.insertAsyncValidation(
|
||||
this.validationAdapter.getSpringValidatorAdapter(), this.adaptViolations,
|
||||
target, method, arguments);
|
||||
}
|
||||
|
||||
Set<ConstraintViolation<Object>> violations;
|
||||
|
||||
if (this.adaptViolations) {
|
||||
@@ -206,4 +230,78 @@ public class MethodValidationInterceptor implements MethodInterceptor {
|
||||
return this.validationAdapter.determineValidationGroups(target, invocation.getMethod());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper class to decorate reactive arguments with async validation.
|
||||
*/
|
||||
private final static class ReactorValidationHelper {
|
||||
|
||||
private static final ReactiveAdapterRegistry reactiveAdapterRegistry =
|
||||
ReactiveAdapterRegistry.getSharedInstance();
|
||||
|
||||
|
||||
public static Object[] insertAsyncValidation(
|
||||
Supplier<SpringValidatorAdapter> validatorAdapterSupplier, boolean adaptViolations,
|
||||
Object target, Method method, Object[] arguments) {
|
||||
|
||||
for (int i = 0; i < method.getParameterCount(); i++) {
|
||||
if (arguments[i] == null) {
|
||||
continue;
|
||||
}
|
||||
Class<?> parameterType = method.getParameterTypes()[i];
|
||||
ReactiveAdapter reactiveAdapter = reactiveAdapterRegistry.getAdapter(parameterType);
|
||||
if (reactiveAdapter == null || reactiveAdapter.isNoValue()) {
|
||||
continue;
|
||||
}
|
||||
Class<?>[] groups = determineValidationGroups(method.getParameters()[i]);
|
||||
if (groups == null) {
|
||||
continue;
|
||||
}
|
||||
SpringValidatorAdapter validatorAdapter = validatorAdapterSupplier.get();
|
||||
MethodParameter param = new MethodParameter(method, i);
|
||||
arguments[i] = (reactiveAdapter.isMultiValue() ?
|
||||
Flux.from(reactiveAdapter.toPublisher(arguments[i])).doOnNext(value ->
|
||||
validate(validatorAdapter, adaptViolations, target, method, param, value, groups)) :
|
||||
Mono.from(reactiveAdapter.toPublisher(arguments[i])).doOnNext(value ->
|
||||
validate(validatorAdapter, adaptViolations, target, method, param, value, groups)));
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Class<?>[] determineValidationGroups(Parameter parameter) {
|
||||
Validated validated = AnnotationUtils.findAnnotation(parameter, Validated.class);
|
||||
if (validated != null) {
|
||||
return validated.value();
|
||||
}
|
||||
Valid valid = AnnotationUtils.findAnnotation(parameter, Valid.class);
|
||||
if (valid != null) {
|
||||
return new Class<?>[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> void validate(
|
||||
SpringValidatorAdapter validatorAdapter, boolean adaptViolations,
|
||||
Object target, Method method, MethodParameter parameter, Object argument, Class<?>[] groups) {
|
||||
|
||||
if (adaptViolations) {
|
||||
Errors errors = new BeanPropertyBindingResult(argument, argument.getClass().getSimpleName());
|
||||
validatorAdapter.validate(argument, errors);
|
||||
if (errors.hasErrors()) {
|
||||
ParameterErrors paramErrors = new ParameterErrors(parameter, argument, errors, null, null, null);
|
||||
List<ParameterValidationResult> results = Collections.singletonList(paramErrors);
|
||||
throw new MethodValidationException(MethodValidationResult.create(target, method, results));
|
||||
}
|
||||
}
|
||||
else {
|
||||
Set<ConstraintViolation<T>> violations = validatorAdapter.validate((T) argument, groups);
|
||||
if (!violations.isEmpty()) {
|
||||
throw new ConstraintViolationException(violations);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user