From c7269feeaae8da24c39fd7deae206d6fd0ce196d Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 16 Aug 2023 12:48:06 +0200 Subject: [PATCH] Align validation metadata handling in PayloadMethodArgumentResolver Reuses ValidationAnnotationUtils which is slightly optimized for the detection of Spring's Validated annotation now, also to the benefit of common web scenarios. Closes gh-21852 --- .../annotation/ValidationAnnotationUtils.java | 27 ++++++++++++------- .../core/annotation/AnnotationUtils.java | 4 +-- .../PayloadMethodArgumentResolver.java | 19 ++++++------- .../PayloadMethodArgumentResolver.java | 19 +++++++------ 4 files changed, 37 insertions(+), 32 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/validation/annotation/ValidationAnnotationUtils.java b/spring-context/src/main/java/org/springframework/validation/annotation/ValidationAnnotationUtils.java index 1ca33e89ac..9a612eeda4 100644 --- a/spring-context/src/main/java/org/springframework/validation/annotation/ValidationAnnotationUtils.java +++ b/spring-context/src/main/java/org/springframework/validation/annotation/ValidationAnnotationUtils.java @@ -26,37 +26,46 @@ import org.springframework.lang.Nullable; * Mainly for internal use within the framework. * * @author Christoph Dreis + * @author Juergen Hoeller * @since 5.3.7 */ public abstract class ValidationAnnotationUtils { private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + /** * Determine any validation hints by the given annotation. - *

This implementation checks for {@code @jakarta.validation.Valid}, - * Spring's {@link org.springframework.validation.annotation.Validated}, - * and custom annotations whose name starts with "Valid". + *

This implementation checks for Spring's + * {@link org.springframework.validation.annotation.Validated}, + * {@code @jakarta.validation.Valid}, and custom annotations whose + * name starts with "Valid" which may optionally declare validation + * hints through the "value" attribute. * @param ann the annotation (potentially a validation annotation) * @return the validation hints to apply (possibly an empty array), * or {@code null} if this annotation does not trigger any validation */ @Nullable public static Object[] determineValidationHints(Annotation ann) { + // Direct presence of @Validated ? + if (ann instanceof Validated validated) { + return validated.value(); + } + // Direct presence of @Valid ? Class annotationType = ann.annotationType(); - String annotationName = annotationType.getName(); - if ("jakarta.validation.Valid".equals(annotationName)) { + if ("jakarta.validation.Valid".equals(annotationType.getName())) { return EMPTY_OBJECT_ARRAY; } + // Meta presence of @Validated ? Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class); if (validatedAnn != null) { - Object hints = validatedAnn.value(); - return convertValidationHints(hints); + return validatedAnn.value(); } + // Custom validation annotation ? if (annotationType.getSimpleName().startsWith("Valid")) { - Object hints = AnnotationUtils.getValue(ann); - return convertValidationHints(hints); + return convertValidationHints(AnnotationUtils.getValue(ann)); } + // No validation triggered return null; } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index a904f39d91..1818174ef9 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -1309,8 +1309,8 @@ public abstract class AnnotationUtils { */ public static boolean isSynthesizedAnnotation(@Nullable Annotation annotation) { try { - return ((annotation != null) && Proxy.isProxyClass(annotation.getClass()) && - (Proxy.getInvocationHandler(annotation) instanceof SynthesizedMergedAnnotationInvocationHandler)); + return (annotation != null && Proxy.isProxyClass(annotation.getClass()) && + Proxy.getInvocationHandler(annotation) instanceof SynthesizedMergedAnnotationInvocationHandler); } catch (SecurityException ex) { // Security settings disallow reflective access to the InvocationHandler: diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/reactive/PayloadMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/reactive/PayloadMethodArgumentResolver.java index bbdd0cbb1f..b057201230 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/reactive/PayloadMethodArgumentResolver.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/reactive/PayloadMethodArgumentResolver.java @@ -33,7 +33,6 @@ import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ResolvableType; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.codec.Decoder; import org.springframework.core.codec.DecodingException; import org.springframework.core.io.buffer.DataBuffer; @@ -54,17 +53,17 @@ import org.springframework.util.StringUtils; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.SmartValidator; import org.springframework.validation.Validator; -import org.springframework.validation.annotation.Validated; +import org.springframework.validation.annotation.ValidationAnnotationUtils; /** * A resolver to extract and decode the payload of a message using a - * {@link Decoder}, where the payload is expected to be a {@link Publisher} of - * {@link DataBuffer DataBuffer}. + * {@link Decoder}, where the payload is expected to be a {@link Publisher} + * of {@link DataBuffer DataBuffer}. * *

Validation is applied if the method argument is annotated with - * {@code @jakarta.validation.Valid} or - * {@link org.springframework.validation.annotation.Validated}. Validation - * failure results in an {@link MethodArgumentNotValidException}. + * {@link org.springframework.validation.annotation.Validated} or + * {@code @jakarta.validation.Valid}. Validation failure results in an + * {@link MethodArgumentNotValidException}. * *

This resolver should be ordered last if {@link #useDefaultResolution} is * set to {@code true} since in that case it supports all types and does not @@ -286,10 +285,8 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol return null; } for (Annotation ann : parameter.getParameterAnnotations()) { - Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class); - if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) { - Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann)); - Object[] validationHints = (hints instanceof Object[] objectHints ? objectHints : new Object[] {hints}); + Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann); + if (validationHints != null) { String name = Conventions.getVariableNameForParameter(parameter); return target -> { BeanPropertyBindingResult bindingResult = new BeanPropertyBindingResult(target, name); diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadMethodArgumentResolver.java index 1cf15fead8..10014be62d 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadMethodArgumentResolver.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadMethodArgumentResolver.java @@ -20,7 +20,6 @@ import java.lang.annotation.Annotation; import java.util.Optional; import org.springframework.core.MethodParameter; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.lang.Nullable; import org.springframework.messaging.Message; import org.springframework.messaging.converter.MessageConversionException; @@ -38,12 +37,16 @@ import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import org.springframework.validation.SmartValidator; import org.springframework.validation.Validator; -import org.springframework.validation.annotation.Validated; +import org.springframework.validation.annotation.ValidationAnnotationUtils; /** * A resolver to extract and convert the payload of a message using a - * {@link MessageConverter}. It also validates the payload using a - * {@link Validator} if the argument is annotated with a Validation annotation. + * {@link MessageConverter}. + * + *

Validation is applied if the method argument is annotated with + * {@link org.springframework.validation.annotation.Validated} or + * {@code @jakarta.validation.Valid}. Validation failure results in an + * {@link MethodArgumentNotValidException}. * *

This {@link HandlerMethodArgumentResolver} should be ordered last as it * supports all types and does not require the {@link Payload} annotation. @@ -196,8 +199,6 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol /** * Validate the payload if applicable. - *

The default implementation checks for {@code @jakarta.validation.Valid}, - * Spring's {@link Validated}, and custom annotations whose name starts with "Valid". * @param message the currently processed message * @param parameter the method parameter * @param target the target payload object @@ -208,10 +209,8 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol return; } for (Annotation ann : parameter.getParameterAnnotations()) { - Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class); - if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) { - Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann)); - Object[] validationHints = (hints instanceof Object[] objectHints ? objectHints : new Object[] {hints}); + Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann); + if (validationHints != null) { BeanPropertyBindingResult bindingResult = new BeanPropertyBindingResult(target, getParameterName(parameter)); if (!ObjectUtils.isEmpty(validationHints) && this.validator instanceof SmartValidator sv) {