Add Visitor to HandlerMethodValidationException
Closes gh-30813
This commit is contained in:
parent
2a126faae7
commit
deaa493644
|
|
@ -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 {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
|
|
|
|||
|
|
@ -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
|
|||
|
||||
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
|
|||
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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
|
||||
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 {
|
|||
}
|
||||
}
|
||||
|
||||
throw new HandlerMethodValidationException(result);
|
||||
throw new HandlerMethodValidationException(
|
||||
result, this.modelAttribitePredicate, this.requestParamPredicate);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -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 {
|
|||
if (codesResolver != null) {
|
||||
adapter.setMessageCodesResolver(codesResolver);
|
||||
}
|
||||
HandlerMethodValidator methodValidator = new HandlerMethodValidator(adapter);
|
||||
adapter.setObjectNameResolver(objectNameResolver);
|
||||
return methodValidator;
|
||||
return new HandlerMethodValidator(adapter, modelAttribitePredicate, requestParamPredicate);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -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
|
|||
* 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) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
|||
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 {
|
|||
(!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 {
|
|||
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 {
|
|||
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 {
|
|||
}
|
||||
}
|
||||
|
||||
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 {
|
|||
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.
|
||||
|
|
|
|||
|
|
@ -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
|
|||
|
||||
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
|
|||
@Nullable
|
||||
private ReactiveAdapterRegistry reactiveAdapterRegistry;
|
||||
|
||||
@Nullable
|
||||
private MethodValidator methodValidator;
|
||||
|
||||
@Nullable
|
||||
private ConfigurableApplicationContext applicationContext;
|
||||
|
||||
|
|
@ -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
|
|||
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
|
||||
@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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
|||
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
|
|||
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;
|
|||
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
|
|||
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
|
|||
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
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
|
||||
@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…
Reference in New Issue