Browse Source

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
pull/31496/head
Juergen Hoeller 1 year ago
parent
commit
c7269feeaa
  1. 27
      spring-context/src/main/java/org/springframework/validation/annotation/ValidationAnnotationUtils.java
  2. 4
      spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
  3. 19
      spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/reactive/PayloadMethodArgumentResolver.java
  4. 19
      spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadMethodArgumentResolver.java

27
spring-context/src/main/java/org/springframework/validation/annotation/ValidationAnnotationUtils.java

@ -26,37 +26,46 @@ import org.springframework.lang.Nullable; @@ -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.
* <p>This implementation checks for {@code @jakarta.validation.Valid},
* Spring's {@link org.springframework.validation.annotation.Validated},
* and custom annotations whose name starts with "Valid".
* <p>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<? extends Annotation> 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;
}

4
spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java

@ -1309,8 +1309,8 @@ public abstract class AnnotationUtils { @@ -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:

19
spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/reactive/PayloadMethodArgumentResolver.java

@ -33,7 +33,6 @@ import org.springframework.core.MethodParameter; @@ -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; @@ -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}.
*
* <p>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}.
*
* <p>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 @@ -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);

19
spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadMethodArgumentResolver.java

@ -20,7 +20,6 @@ import java.lang.annotation.Annotation; @@ -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; @@ -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}.
*
* <p>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}.
*
* <p>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 @@ -196,8 +199,6 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol
/**
* Validate the payload if applicable.
* <p>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 @@ -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) {

Loading…
Cancel
Save