diff --git a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java index e21927e331..0d12d23572 100644 --- a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java +++ b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -72,6 +72,7 @@ public class HandlerMethod extends AnnotatedMethod { /** Logger that is available to subclasses. */ protected static final Log logger = LogFactory.getLog(HandlerMethod.class); + private final Object bean; @Nullable @@ -116,8 +117,8 @@ public class HandlerMethod extends AnnotatedMethod { this.beanFactory = null; this.messageSource = messageSource; this.beanType = ClassUtils.getUserClass(bean); - this.validateArguments = MethodValidationInitializer.checkArguments(this.beanType, getMethodParameters()); - this.validateReturnValue = MethodValidationInitializer.checkReturnValue(this.beanType, getBridgedMethod()); + this.validateArguments = false; + this.validateReturnValue = false; evaluateResponseStatus(); this.description = initDescription(this.beanType, method); } @@ -132,8 +133,8 @@ public class HandlerMethod extends AnnotatedMethod { this.beanFactory = null; this.messageSource = null; this.beanType = ClassUtils.getUserClass(bean); - this.validateArguments = MethodValidationInitializer.checkArguments(this.beanType, getMethodParameters()); - this.validateReturnValue = MethodValidationInitializer.checkReturnValue(this.beanType, getBridgedMethod()); + this.validateArguments = false; + this.validateReturnValue = false; evaluateResponseStatus(); this.description = initDescription(this.beanType, getMethod()); } @@ -166,8 +167,8 @@ public class HandlerMethod extends AnnotatedMethod { throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'"); } this.beanType = ClassUtils.getUserClass(beanType); - this.validateArguments = MethodValidationInitializer.checkArguments(this.beanType, getMethodParameters()); - this.validateReturnValue = MethodValidationInitializer.checkReturnValue(this.beanType, getBridgedMethod()); + this.validateArguments = false; + this.validateReturnValue = false; evaluateResponseStatus(); this.description = initDescription(this.beanType, method); } @@ -176,31 +177,24 @@ public class HandlerMethod extends AnnotatedMethod { * Copy constructor for use in subclasses. */ protected HandlerMethod(HandlerMethod handlerMethod) { - super(handlerMethod); - this.bean = handlerMethod.bean; - this.beanFactory = handlerMethod.beanFactory; - this.messageSource = handlerMethod.messageSource; - this.beanType = handlerMethod.beanType; - this.validateArguments = handlerMethod.validateArguments; - this.validateReturnValue = handlerMethod.validateReturnValue; - this.responseStatus = handlerMethod.responseStatus; - this.responseStatusReason = handlerMethod.responseStatusReason; - this.description = handlerMethod.description; - this.resolvedFromHandlerMethod = handlerMethod.resolvedFromHandlerMethod; + this(handlerMethod, null, false); } /** - * Re-create HandlerMethod with the resolved handler. + * Re-create HandlerMethod with additional input. */ - private HandlerMethod(HandlerMethod handlerMethod, Object handler) { + private HandlerMethod(HandlerMethod handlerMethod, @Nullable Object handler, boolean initValidateFlags) { super(handlerMethod); - Assert.notNull(handler, "Handler object is required"); - this.bean = handler; + this.bean = (handler != null ? handler : handlerMethod.bean); this.beanFactory = handlerMethod.beanFactory; this.messageSource = handlerMethod.messageSource; this.beanType = handlerMethod.beanType; - this.validateArguments = handlerMethod.validateArguments; - this.validateReturnValue = handlerMethod.validateReturnValue; + this.validateArguments = (initValidateFlags ? + MethodValidationInitializer.checkArguments(this.beanType, getMethodParameters()) : + handlerMethod.validateArguments); + this.validateReturnValue = (initValidateFlags ? + MethodValidationInitializer.checkReturnValue(this.beanType, getBridgedMethod()) : + handlerMethod.validateReturnValue); this.responseStatus = handlerMethod.responseStatus; this.responseStatusReason = handlerMethod.responseStatusReason; this.resolvedFromHandlerMethod = handlerMethod; @@ -313,6 +307,15 @@ public class HandlerMethod extends AnnotatedMethod { return this.resolvedFromHandlerMethod; } + /** + * Re-create the HandlerMethod and initialize + * {@link #shouldValidateArguments()} and {@link #shouldValidateReturnValue()}. + * @since 6.1.3 + */ + public HandlerMethod createWithValidateFlags() { + return new HandlerMethod(this, null, true); + } + /** * If the provided instance contains a bean name rather than an object instance, * the bean name is resolved before a {@link HandlerMethod} is created and returned. @@ -323,7 +326,8 @@ public class HandlerMethod extends AnnotatedMethod { Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory"); handler = this.beanFactory.getBean(beanName); } - return new HandlerMethod(this, handler); + Assert.notNull(handler, "No handler instance"); + return new HandlerMethod(this, handler, false); } /** @@ -392,6 +396,9 @@ public class HandlerMethod extends AnnotatedMethod { */ private static class MethodValidationInitializer { + private static final boolean BEAN_VALIDATION_PRESENT = + ClassUtils.isPresent("jakarta.validation.Validator", HandlerMethod.class.getClassLoader()); + private static final Predicate> CONSTRAINT_PREDICATE = MergedAnnotationPredicates.typeIn("jakarta.validation.Constraint"); @@ -399,7 +406,7 @@ public class HandlerMethod extends AnnotatedMethod { MergedAnnotationPredicates.typeIn("jakarta.validation.Valid"); public static boolean checkArguments(Class beanType, MethodParameter[] parameters) { - if (AnnotationUtils.findAnnotation(beanType, Validated.class) == null) { + if (BEAN_VALIDATION_PRESENT && AnnotationUtils.findAnnotation(beanType, Validated.class) == null) { for (MethodParameter param : parameters) { MergedAnnotations merged = MergedAnnotations.from(param.getParameterAnnotations()); if (merged.stream().anyMatch(CONSTRAINT_PREDICATE)) { @@ -419,7 +426,7 @@ public class HandlerMethod extends AnnotatedMethod { } public static boolean checkReturnValue(Class beanType, Method method) { - if (AnnotationUtils.findAnnotation(beanType, Validated.class) == null) { + if (BEAN_VALIDATION_PRESENT && AnnotationUtils.findAnnotation(beanType, Validated.class) == null) { MergedAnnotations merged = MergedAnnotations.from(method, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY); return merged.stream().anyMatch(CONSTRAINT_PREDICATE.or(VALID_PREDICATE)); } diff --git a/spring-web/src/test/java/org/springframework/web/method/HandlerMethodTests.java b/spring-web/src/test/java/org/springframework/web/method/HandlerMethodTests.java index c8cfd8bf1d..82e903db1c 100644 --- a/spring-web/src/test/java/org/springframework/web/method/HandlerMethodTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/HandlerMethodTests.java @@ -86,7 +86,7 @@ class HandlerMethodTests { private static HandlerMethod getHandlerMethod(Object target, String methodName) { Method method = ClassUtils.getMethod(target.getClass(), methodName, (Class[]) null); - return new HandlerMethod(target, method); + return new HandlerMethod(target, method).createWithValidateFlags(); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java index 237b36c7d2..bc5c8126b7 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -524,6 +524,9 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap HandlerMethod handlerMethod = createHandlerMethod(handler, method); validateMethodMapping(handlerMethod, mapping); + // Enable method validation, if applicable + handlerMethod = handlerMethod.createWithValidateFlags(); + Set directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping); for (String path : directPaths) { this.pathLookup.add(path, mapping); 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 818a0cad60..4ed9a43af3 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -339,7 +339,7 @@ public class MethodValidationTests { @SuppressWarnings("unchecked") private static HandlerMethod handlerMethod(T controller, Consumer mockCallConsumer) { Method method = ResolvableMethod.on((Class) controller.getClass()).mockCall(mockCallConsumer).method(); - return new HandlerMethod(controller, method); + return new HandlerMethod(controller, method).createWithValidateFlags(); } private static MockServerHttpRequest.BodyBuilder request() { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java index f9f5c68e42..600e308651 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -632,6 +632,9 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap HandlerMethod handlerMethod = createHandlerMethod(handler, method); validateMethodMapping(handlerMethod, mapping); + // Enable method validation, if applicable + handlerMethod = handlerMethod.createWithValidateFlags(); + Set directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping); for (String path : directPaths) { this.pathLookup.add(path, mapping); 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 4cdace9581..026ecc3cbf 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -301,7 +301,7 @@ public class MethodValidationTests { @SuppressWarnings("unchecked") private static HandlerMethod handlerMethod(T controller, Consumer mockCallConsumer) { Method method = ResolvableMethod.on((Class) controller.getClass()).mockCall(mockCallConsumer).method(); - return new HandlerMethod(controller, method); + return new HandlerMethod(controller, method).createWithValidateFlags(); } @SuppressWarnings("SameParameterValue")