Browse Source

Add Visitor to HandlerMethodValidationException

Closes gh-30813
pull/30820/head
rstoyanchev 2 years ago
parent
commit
deaa493644
  1. 14
      spring-context/src/main/java/org/springframework/validation/method/MethodValidationResult.java
  2. 156
      spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidationException.java
  3. 22
      spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.java
  4. 4
      spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java
  5. 253
      spring-web/src/test/java/org/springframework/web/method/support/HandlerMethodValidationExceptionTests.java
  6. 38
      spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java
  7. 19
      spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java
  8. 2
      spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MethodValidationTests.java
  9. 23
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
  10. 2
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MethodValidationTests.java

14
spring-context/src/main/java/org/springframework/validation/method/MethodValidationResult.java

@ -79,9 +79,10 @@ public interface MethodValidationResult { @@ -79,9 +79,10 @@ public interface MethodValidationResult {
List<ParameterValidationResult> getAllValidationResults();
/**
* Return only validation results for method parameters with errors directly
* on them. This does not include Object method parameters with nested
* errors on their fields and properties.
* Return the subset of {@link #getAllValidationResults() allValidationResults}
* that includes method parameters with validation errors directly on method
* argument values. This excludes {@link #getBeanResults() beanResults} with
* nested errors on their fields and properties.
*/
default List<ParameterValidationResult> getValueResults() {
return getAllValidationResults().stream()
@ -90,9 +91,10 @@ public interface MethodValidationResult { @@ -90,9 +91,10 @@ public interface MethodValidationResult {
}
/**
* Return only validation results for Object method parameters with nested
* errors on their fields and properties. This excludes method parameters
* with errors directly on them.
* Return the subset of {@link #getAllValidationResults() allValidationResults}
* that includes Object method parameters with nested errors on their fields
* and properties. This excludes {@link #getValueResults() valueResults} with
* validation errors directly on method arguments.
*/
default List<ParameterErrors> getBeanResults() {
return getAllValidationResults().stream()

156
spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidationException.java

@ -19,11 +19,24 @@ package org.springframework.web.method.annotation; @@ -19,11 +19,24 @@ package org.springframework.web.method.annotation;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Locale;
import java.util.function.Predicate;
import org.springframework.context.MessageSource;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.validation.method.MethodValidationResult;
import org.springframework.validation.method.ParameterErrors;
import org.springframework.validation.method.ParameterValidationResult;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.MatrixVariable;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.util.BindErrorUtils;
@ -43,10 +56,24 @@ public class HandlerMethodValidationException extends ResponseStatusException im @@ -43,10 +56,24 @@ public class HandlerMethodValidationException extends ResponseStatusException im
private final MethodValidationResult validationResult;
private final Predicate<MethodParameter> modelAttribitePredicate;
private final Predicate<MethodParameter> requestParamPredicate;
public HandlerMethodValidationException(MethodValidationResult validationResult) {
this(validationResult,
param -> param.hasParameterAnnotation(ModelAttribute.class),
param -> param.hasParameterAnnotation(RequestParam.class));
}
public HandlerMethodValidationException(MethodValidationResult validationResult,
Predicate<MethodParameter> modelAttribitePredicate, Predicate<MethodParameter> requestParamPredicate) {
super(initHttpStatus(validationResult), "Validation failure", null, null, null);
this.validationResult = validationResult;
this.modelAttribitePredicate = modelAttribitePredicate;
this.requestParamPredicate = requestParamPredicate;
}
private static HttpStatus initHttpStatus(MethodValidationResult validationResult) {
@ -84,4 +111,133 @@ public class HandlerMethodValidationException extends ResponseStatusException im @@ -84,4 +111,133 @@ public class HandlerMethodValidationException extends ResponseStatusException im
return this.validationResult.getAllValidationResults();
}
/**
* Provide a {@link Visitor Visitor} to handle {@link ParameterValidationResult}s
* through callback methods organized by controller method parameter type.
*/
public void visitResults(Visitor visitor) {
for (ParameterValidationResult result : getAllValidationResults()) {
MethodParameter param = result.getMethodParameter();
CookieValue cookieValue = param.getParameterAnnotation(CookieValue.class);
if (cookieValue != null) {
visitor.cookieValue(cookieValue, result);
continue;
}
MatrixVariable matrixVariable = param.getParameterAnnotation(MatrixVariable.class);
if (matrixVariable != null) {
visitor.matrixVariable(matrixVariable, result);
continue;
}
if (this.modelAttribitePredicate.test(param)) {
ModelAttribute modelAttribute = param.getParameterAnnotation(ModelAttribute.class);
visitor.modelAttribute(modelAttribute, asErrors(result));
continue;
}
PathVariable pathVariable = param.getParameterAnnotation(PathVariable.class);
if (pathVariable != null) {
visitor.pathVariable(pathVariable, result);
continue;
}
RequestBody requestBody = param.getParameterAnnotation(RequestBody.class);
if (requestBody != null) {
visitor.requestBody(requestBody, asErrors(result));
continue;
}
RequestHeader requestHeader = param.getParameterAnnotation(RequestHeader.class);
if (requestHeader != null) {
visitor.requestHeader(requestHeader, result);
continue;
}
if (this.requestParamPredicate.test(param)) {
RequestParam requestParam = param.getParameterAnnotation(RequestParam.class);
visitor.requestParam(requestParam, result);
continue;
}
RequestPart requestPart = param.getParameterAnnotation(RequestPart.class);
if (requestPart != null) {
visitor.requestPart(requestPart, asErrors(result));
continue;
}
visitor.other(result);
}
}
private static ParameterErrors asErrors(ParameterValidationResult result) {
Assert.state(result instanceof ParameterErrors, "Expected ParameterErrors");
return (ParameterErrors) result;
}
/**
* Contract to handle validation results with callbacks by controller method
* parameter type, with {@link #other} serving as the fallthrough.
*/
public interface Visitor {
/**
* Handle results for {@code @CookieValue} method parameters.
* @param cookieValue the annotation declared on the parameter
* @param result the validation result
*/
void cookieValue(CookieValue cookieValue, ParameterValidationResult result);
/**
* Handle results for {@code @MatrixVariable} method parameters.
* @param matrixVariable the annotation declared on the parameter
* @param result the validation result
*/
void matrixVariable(MatrixVariable matrixVariable, ParameterValidationResult result);
/**
* Handle results for {@code @ModelAttribute} method parameters.
* @param modelAttribute the optional {@code ModelAttribute} annotation,
* possibly {@code null} if the method parameter is declared without it.
* @param errors the validation errors
*/
void modelAttribute(@Nullable ModelAttribute modelAttribute, ParameterErrors errors);
/**
* Handle results for {@code @PathVariable} method parameters.
* @param pathVariable the annotation declared on the parameter
* @param result the validation result
*/
void pathVariable(PathVariable pathVariable, ParameterValidationResult result);
/**
* Handle results for {@code @RequestBody} method parameters.
* @param requestBody the annotation declared on the parameter
* @param errors the validation error
*/
void requestBody(RequestBody requestBody, ParameterErrors errors);
/**
* Handle results for {@code @RequestHeader} method parameters.
* @param requestHeader the annotation declared on the parameter
* @param result the validation result
*/
void requestHeader(RequestHeader requestHeader, ParameterValidationResult result);
/**
* Handle results for {@code @RequestParam} method parameters.
* @param requestParam the optional {@code RequestParam} annotation,
* possibly {@code null} if the method parameter is declared without it.
* @param result the validation result
*/
void requestParam(@Nullable RequestParam requestParam, ParameterValidationResult result);
/**
* Handle results for {@code @RequestPart} method parameters.
* @param requestPart the annotation declared on the parameter
* @param errors the validation errors
*/
void requestPart(RequestPart requestPart, ParameterErrors errors);
/**
* Handle other results that aren't any of the above.
* @param result the validation result
*/
void other(ParameterValidationResult result);
}
}

22
spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.java

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package org.springframework.web.method.annotation;
import java.lang.reflect.Method;
import java.util.function.Predicate;
import jakarta.validation.Validator;
@ -54,9 +55,17 @@ public final class HandlerMethodValidator implements MethodValidator { @@ -54,9 +55,17 @@ public final class HandlerMethodValidator implements MethodValidator {
private final MethodValidationAdapter validationAdapter;
private final Predicate<MethodParameter> modelAttribitePredicate;
private final Predicate<MethodParameter> requestParamPredicate;
private HandlerMethodValidator(MethodValidationAdapter validationAdapter,
Predicate<MethodParameter> modelAttribitePredicate, Predicate<MethodParameter> requestParamPredicate) {
private HandlerMethodValidator(MethodValidationAdapter validationAdapter) {
this.validationAdapter = validationAdapter;
this.modelAttribitePredicate = modelAttribitePredicate;
this.requestParamPredicate = requestParamPredicate;
}
@ -93,7 +102,8 @@ public final class HandlerMethodValidator implements MethodValidator { @@ -93,7 +102,8 @@ public final class HandlerMethodValidator implements MethodValidator {
}
}
throw new HandlerMethodValidationException(result);
throw new HandlerMethodValidationException(
result, this.modelAttribitePredicate, this.requestParamPredicate);
}
@Override
@ -130,11 +140,13 @@ public final class HandlerMethodValidator implements MethodValidator { @@ -130,11 +140,13 @@ public final class HandlerMethodValidator implements MethodValidator {
*/
@Nullable
public static MethodValidator from(
@Nullable WebBindingInitializer initializer, @Nullable ParameterNameDiscoverer paramNameDiscoverer) {
@Nullable WebBindingInitializer initializer, @Nullable ParameterNameDiscoverer paramNameDiscoverer,
Predicate<MethodParameter> modelAttribitePredicate, Predicate<MethodParameter> requestParamPredicate) {
if (initializer instanceof ConfigurableWebBindingInitializer configurableInitializer) {
if (configurableInitializer.getValidator() instanceof Validator validator) {
MethodValidationAdapter adapter = new MethodValidationAdapter(validator);
adapter.setObjectNameResolver(objectNameResolver);
if (paramNameDiscoverer != null) {
adapter.setParameterNameDiscoverer(paramNameDiscoverer);
}
@ -142,9 +154,7 @@ public final class HandlerMethodValidator implements MethodValidator { @@ -142,9 +154,7 @@ public final class HandlerMethodValidator implements MethodValidator {
if (codesResolver != null) {
adapter.setMessageCodesResolver(codesResolver);
}
HandlerMethodValidator methodValidator = new HandlerMethodValidator(adapter);
adapter.setObjectNameResolver(objectNameResolver);
return methodValidator;
return new HandlerMethodValidator(adapter, modelAttribitePredicate, requestParamPredicate);
}
}
return null;

4
spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
@ -127,7 +127,7 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu @@ -127,7 +127,7 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu
* the given method parameter.
*/
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
public HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {

253
spring-web/src/test/java/org/springframework/web/method/support/HandlerMethodValidationExceptionTests.java

@ -0,0 +1,253 @@ @@ -0,0 +1,253 @@
/*
* 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.web.method.support;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.function.Predicate;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.method.MethodValidationResult;
import org.springframework.validation.method.ParameterErrors;
import org.springframework.validation.method.ParameterValidationResult;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.MatrixVariable;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.HandlerMethodValidationException;
import org.springframework.web.testfixture.method.ResolvableMethod;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link HandlerMethodValidationException}.
*
* @author Rossen Stoyanchev
*/
public class HandlerMethodValidationExceptionTests {
private static final Person person = new Person("Faustino1234");
private static final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
private final HandlerMethod handlerMethod = handlerMethod(new ValidController(),
controller -> controller.handle(person, person, person, person, "", "", "", "", "", ""));
private final TestVisitor visitor = new TestVisitor();
@Test
void traverse() {
HandlerMethodValidationException ex =
new HandlerMethodValidationException(createMethodValidationResult(this.handlerMethod),
new MvcParamPredicate(ModelAttribute.class),
new MvcParamPredicate(RequestParam.class));
ex.visitResults(this.visitor);
assertThat(this.visitor.getOutput()).isEqualTo("""
@ModelAttribute: modelAttribute1, @ModelAttribute: modelAttribute2, \
@RequestBody: requestBody, @RequestPart: requestPart, \
@RequestParam: requestParam1, @RequestParam: requestParam2, \
@RequestHeader: header, @PathVariable: pathVariable, \
@CookieValue: cookie, @MatrixVariable: matrixVariable""");
}
@Test
void traverseRemaining() {
HandlerMethodValidationException ex =
new HandlerMethodValidationException(createMethodValidationResult(this.handlerMethod));
ex.visitResults(this.visitor);
assertThat(this.visitor.getOutput()).isEqualTo("""
Other: modelAttribute1, @ModelAttribute: modelAttribute2, \
@RequestBody: requestBody, @RequestPart: requestPart, \
Other: requestParam1, @RequestParam: requestParam2, \
@RequestHeader: header, @PathVariable: pathVariable, \
@CookieValue: cookie, @MatrixVariable: matrixVariable""");
}
@SuppressWarnings("unchecked")
private static <T> HandlerMethod handlerMethod(T controller, Consumer<T> mockCallConsumer) {
Method method = ResolvableMethod.on((Class<T>) controller.getClass()).mockCall(mockCallConsumer).method();
HandlerMethod hm = new HandlerMethod(controller, method);
for (MethodParameter parameter : hm.getMethodParameters()) {
parameter.initParameterNameDiscovery(parameterNameDiscoverer);
}
return hm;
}
private static MethodValidationResult createMethodValidationResult(HandlerMethod handlerMethod) {
return MethodValidationResult.create(
handlerMethod.getBean(), handlerMethod.getMethod(),
Arrays.stream(handlerMethod.getMethodParameters())
.map(param -> {
if (param.hasParameterAnnotation(Valid.class)) {
Errors errors = new BeanPropertyBindingResult(person, param.getParameterName());
errors.rejectValue("name", "Size.person.name");
return new ParameterErrors(param, person, errors, null, null, null);
}
else {
MessageSourceResolvable error = new DefaultMessageSourceResolvable("Size");
return new ParameterValidationResult(param, "123", List.of(error));
}
})
.toList());
}
@SuppressWarnings("unused")
private record Person(@Size(min = 1, max = 10) String name) {
@Override
public String name() {
return this.name;
}
}
@SuppressWarnings({"unused", "SameParameterValue", "UnusedReturnValue"})
@RestController
static class ValidController {
void handle(
@Valid Person modelAttribute1,
@Valid @ModelAttribute Person modelAttribute2,
@Valid @RequestBody Person requestBody,
@Valid @RequestPart Person requestPart,
@Size(min = 5) String requestParam1,
@Size(min = 5) @RequestParam String requestParam2,
@Size(min = 5) @RequestHeader String header,
@Size(min = 5) @PathVariable String pathVariable,
@Size(min = 5) @CookieValue String cookie,
@Size(min = 5) @MatrixVariable String matrixVariable) {
}
}
private record MvcParamPredicate(Class<? extends Annotation> type) implements Predicate<MethodParameter> {
@Override
public boolean test(MethodParameter param) {
return (param.hasParameterAnnotation(this.type) ||
(isDefaultParameter(param) && !hasMvcAnnotation(param)));
}
private boolean isDefaultParameter(MethodParameter param) {
boolean simpleType = BeanUtils.isSimpleValueType(param.getParameterType());
return ((this.type.equals(RequestParam.class) && simpleType) ||
(this.type.equals(ModelAttribute.class) && !simpleType));
}
private boolean hasMvcAnnotation(MethodParameter param) {
return Arrays.stream(param.getParameterAnnotations())
.map(Annotation::annotationType)
.anyMatch(type -> type.getPackage().equals(RequestParam.class.getPackage()));
}
}
private static class TestVisitor implements HandlerMethodValidationException.Visitor {
private final StringJoiner joiner = new StringJoiner(", ");
public String getOutput() {
return this.joiner.toString();
}
@Override
public void cookieValue(CookieValue cookieValue, ParameterValidationResult result) {
handle(cookieValue, result);
}
@Override
public void matrixVariable(MatrixVariable matrixVariable, ParameterValidationResult result) {
handle(matrixVariable, result);
}
@Override
public void modelAttribute(@Nullable ModelAttribute modelAttribute, ParameterErrors errors) {
handle("@ModelAttribute", errors);
}
@Override
public void pathVariable(PathVariable pathVariable, ParameterValidationResult result) {
handle(pathVariable, result);
}
@Override
public void requestBody(RequestBody requestBody, ParameterErrors errors) {
handle(requestBody, errors);
}
@Override
public void requestHeader(RequestHeader requestHeader, ParameterValidationResult result) {
handle(requestHeader, result);
}
@Override
public void requestParam(@Nullable RequestParam requestParam, ParameterValidationResult result) {
handle("@RequestParam", result);
}
@Override
public void requestPart(RequestPart requestPart, ParameterErrors errors) {
handle(requestPart, errors);
}
@Override
public void other(ParameterValidationResult result) {
handle("Other", result);
}
private void handle(Annotation annotation, ParameterValidationResult result) {
handle("@" + annotation.annotationType().getSimpleName(), result);
}
private void handle(String tag, ParameterValidationResult result) {
this.joiner.add(tag + ": " + result.getMethodParameter().getParameterName());
}
}
}

38
spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java

@ -24,6 +24,7 @@ import java.util.List; @@ -24,6 +24,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -33,23 +34,28 @@ import org.springframework.context.ApplicationContext; @@ -33,23 +34,28 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.validation.method.MethodValidator;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.method.ControllerAdviceBean;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver;
import org.springframework.web.method.annotation.HandlerMethodValidator;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod;
import org.springframework.web.service.invoker.RequestParamArgumentResolver;
/**
* Package-private class to assist {@link RequestMappingHandlerAdapter} with
@ -82,9 +88,12 @@ class ControllerMethodResolver { @@ -82,9 +88,12 @@ class ControllerMethodResolver {
(!AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class) &&
AnnotatedElementUtils.hasAnnotation(method, ModelAttribute.class));
private final static boolean BEAN_VALIDATION_PRESENT =
ClassUtils.isPresent("jakarta.validation.Validator", HandlerMethod.class.getClassLoader());
private static final Log logger = LogFactory.getLog(ControllerMethodResolver.class);
private final List<SyncHandlerMethodArgumentResolver> initBinderResolvers;
private final List<HandlerMethodArgumentResolver> modelAttributeResolvers;
@ -117,7 +126,7 @@ class ControllerMethodResolver { @@ -117,7 +126,7 @@ class ControllerMethodResolver {
ControllerMethodResolver(
ArgumentResolverConfigurer customResolvers, ReactiveAdapterRegistry adapterRegistry,
ConfigurableApplicationContext context, List<HttpMessageReader<?>> readers,
@Nullable MethodValidator methodValidator) {
@Nullable WebBindingInitializer webBindingInitializer) {
Assert.notNull(customResolvers, "ArgumentResolverConfigurer is required");
Assert.notNull(adapterRegistry, "ReactiveAdapterRegistry is required");
@ -129,7 +138,15 @@ class ControllerMethodResolver { @@ -129,7 +138,15 @@ class ControllerMethodResolver {
this.requestMappingResolvers = requestMappingResolvers(customResolvers, adapterRegistry, context, readers);
this.exceptionHandlerResolvers = exceptionHandlerResolvers(customResolvers, adapterRegistry, context);
this.reactiveAdapterRegistry = adapterRegistry;
this.methodValidator = methodValidator;
if (BEAN_VALIDATION_PRESENT) {
this.methodValidator = HandlerMethodValidator.from(webBindingInitializer, null,
methodParamPredicate(this.requestMappingResolvers, ModelAttributeMethodArgumentResolver.class),
methodParamPredicate(this.requestMappingResolvers, RequestParamArgumentResolver.class));
}
else {
this.methodValidator = null;
}
initControllerAdviceCaches(context);
}
@ -258,6 +275,19 @@ class ControllerMethodResolver { @@ -258,6 +275,19 @@ class ControllerMethodResolver {
}
}
private static Predicate<MethodParameter> methodParamPredicate(
List<HandlerMethodArgumentResolver> resolvers, Class<?> resolverType) {
return parameter -> {
for (HandlerMethodArgumentResolver resolver : resolvers) {
if (resolver.supportsParameter(parameter)) {
return resolverType.isInstance(resolver);
}
}
return false;
};
}
/**
* Return an {@link InvocableHandlerMethod} for the given
@ -383,6 +413,10 @@ class ControllerMethodResolver { @@ -383,6 +413,10 @@ class ControllerMethodResolver {
return invocable;
}
public boolean hasMethodValidator() {
return (this.methodValidator != null);
}
/**
* Return the handler for the type-level {@code @SessionAttributes} annotation
* based on the given controller method.

19
spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java

@ -33,12 +33,9 @@ import org.springframework.http.codec.HttpMessageReader; @@ -33,12 +33,9 @@ import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.method.MethodValidator;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.HandlerMethodValidator;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.DispatchExceptionHandler;
import org.springframework.web.reactive.HandlerAdapter;
@ -60,9 +57,6 @@ public class RequestMappingHandlerAdapter @@ -60,9 +57,6 @@ public class RequestMappingHandlerAdapter
private static final Log logger = LogFactory.getLog(RequestMappingHandlerAdapter.class);
private final static boolean BEAN_VALIDATION_PRESENT =
ClassUtils.isPresent("jakarta.validation.Validator", HandlerMethod.class.getClassLoader());
private List<HttpMessageReader<?>> messageReaders = Collections.emptyList();
@ -75,9 +69,6 @@ public class RequestMappingHandlerAdapter @@ -75,9 +69,6 @@ public class RequestMappingHandlerAdapter
@Nullable
private ReactiveAdapterRegistry reactiveAdapterRegistry;
@Nullable
private MethodValidator methodValidator;
@Nullable
private ConfigurableApplicationContext applicationContext;
@ -179,12 +170,10 @@ public class RequestMappingHandlerAdapter @@ -179,12 +170,10 @@ public class RequestMappingHandlerAdapter
if (this.reactiveAdapterRegistry == null) {
this.reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
}
if (BEAN_VALIDATION_PRESENT) {
this.methodValidator = HandlerMethodValidator.from(this.webBindingInitializer, null);
}
this.methodResolver = new ControllerMethodResolver(this.argumentResolverConfigurer,
this.reactiveAdapterRegistry, this.applicationContext, this.messageReaders, this.methodValidator);
this.methodResolver = new ControllerMethodResolver(
this.argumentResolverConfigurer, this.reactiveAdapterRegistry, this.applicationContext,
this.messageReaders, this.webBindingInitializer);
this.modelInitializer = new ModelInitializer(this.methodResolver, this.reactiveAdapterRegistry);
}
@ -202,7 +191,7 @@ public class RequestMappingHandlerAdapter @@ -202,7 +191,7 @@ public class RequestMappingHandlerAdapter
InitBinderBindingContext bindingContext = new InitBinderBindingContext(
this.webBindingInitializer, this.methodResolver.getInitBinderMethods(handlerMethod),
this.methodValidator != null && handlerMethod.shouldValidateArguments());
this.methodResolver.hasMethodValidator() && handlerMethod.shouldValidateArguments());
InvocableHandlerMethod invocableMethod = this.methodResolver.getRequestMappingMethod(handlerMethod);

2
spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MethodValidationTests.java

@ -37,7 +37,6 @@ import reactor.test.StepVerifier; @@ -37,7 +37,6 @@ import reactor.test.StepVerifier;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.Validator;
@ -300,7 +299,6 @@ public class MethodValidationTests { @@ -300,7 +299,6 @@ public class MethodValidationTests {
@SuppressWarnings("unchecked")
private static <T> HandlerMethod handlerMethod(T controller, Consumer<T> mockCallConsumer) {
Assert.isTrue(!(controller instanceof Class<?>), "Expected controller instance");
Method method = ResolvableMethod.on((Class<T>) controller.getClass()).mockCall(mockCallConsumer).method();
return new HandlerMethod(controller, method);
}

23
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java

@ -24,6 +24,7 @@ import java.util.Map; @@ -24,6 +24,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@ -36,6 +37,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory; @@ -36,6 +37,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.annotation.AnnotatedElementUtils;
@ -77,6 +79,7 @@ import org.springframework.web.method.annotation.ExpressionValueMethodArgumentRe @@ -77,6 +79,7 @@ import org.springframework.web.method.annotation.ExpressionValueMethodArgumentRe
import org.springframework.web.method.annotation.HandlerMethodValidator;
import org.springframework.web.method.annotation.InitBinderDataBinderFactory;
import org.springframework.web.method.annotation.MapMethodProcessor;
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor;
import org.springframework.web.method.annotation.ModelFactory;
import org.springframework.web.method.annotation.ModelMethodProcessor;
import org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver;
@ -91,6 +94,7 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler; @@ -91,6 +94,7 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.service.invoker.RequestParamArgumentResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver;
@ -569,7 +573,11 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter @@ -569,7 +573,11 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
if (BEAN_VALIDATION_PRESENT) {
this.methodValidator = HandlerMethodValidator.from(this.webBindingInitializer, this.parameterNameDiscoverer);
List<HandlerMethodArgumentResolver> resolvers = this.argumentResolvers.getResolvers();
this.methodValidator = HandlerMethodValidator.from(
this.webBindingInitializer, this.parameterNameDiscoverer,
methodParamPredicate(resolvers, ModelAttributeMethodProcessor.class),
methodParamPredicate(resolvers, RequestParamArgumentResolver.class));
}
}
@ -769,6 +777,19 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter @@ -769,6 +777,19 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
return handlers;
}
private static Predicate<MethodParameter> methodParamPredicate(
List<HandlerMethodArgumentResolver> resolvers, Class<?> resolverType) {
return parameter -> {
for (HandlerMethodArgumentResolver resolver : resolvers) {
if (resolver.supportsParameter(parameter)) {
return resolverType.isInstance(resolver);
}
}
return false;
};
}
/**
* Always return {@code true} since any method argument and return value

2
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MethodValidationTests.java

@ -33,7 +33,6 @@ import org.junit.jupiter.api.Test; @@ -33,7 +33,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.Validator;
@ -259,7 +258,6 @@ public class MethodValidationTests { @@ -259,7 +258,6 @@ public class MethodValidationTests {
@SuppressWarnings("unchecked")
private static <T> HandlerMethod handlerMethod(T controller, Consumer<T> mockCallConsumer) {
Assert.isTrue(!(controller instanceof Class<?>), "Expected controller instance");
Method method = ResolvableMethod.on((Class<T>) controller.getClass()).mockCall(mockCallConsumer).method();
return new HandlerMethod(controller, method);
}

Loading…
Cancel
Save