Refactoring in MethodValidationAdapter
Extract the default logic for resolving the name of an @Valid parameter into an ObjectNameResolver, and use it when there isn't one configured. See gh-30644
This commit is contained in:
@@ -70,9 +70,11 @@ import org.springframework.validation.annotation.Validated;
|
|||||||
*/
|
*/
|
||||||
public class MethodValidationAdapter implements MethodValidator {
|
public class MethodValidationAdapter implements MethodValidator {
|
||||||
|
|
||||||
private static final Comparator<ParameterValidationResult> RESULT_COMPARATOR = new ResultComparator();
|
private static final ObjectNameResolver defaultObjectNameResolver = new DefaultObjectNameResolver();
|
||||||
|
|
||||||
private static final MethodValidationResult EMPTY_RESULT = new EmptyMethodValidationResult();
|
private static final Comparator<ParameterValidationResult> resultComparator = new ResultComparator();
|
||||||
|
|
||||||
|
private static final MethodValidationResult emptyResult = new EmptyMethodValidationResult();
|
||||||
|
|
||||||
|
|
||||||
private final Supplier<Validator> validator;
|
private final Supplier<Validator> validator;
|
||||||
@@ -83,8 +85,7 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||||||
|
|
||||||
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
||||||
|
|
||||||
@Nullable
|
private ObjectNameResolver objectNameResolver = defaultObjectNameResolver;
|
||||||
private BindingResultNameResolver objectNameResolver;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -142,8 +143,10 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the ParameterNameDiscoverer to use to resolve method parameter names
|
* Set the {@code ParameterNameDiscoverer} to discover method parameter names
|
||||||
* that is in turn used to create error codes for {@link MessageSourceResolvable}.
|
* with to create error codes for {@link MessageSourceResolvable}. Used only
|
||||||
|
* when {@link MethodParameter}s are not passed into
|
||||||
|
* {@link #validateArguments} or {@link #validateReturnValue}.
|
||||||
* <p>Default is {@link org.springframework.core.DefaultParameterNameDiscoverer}.
|
* <p>Default is {@link org.springframework.core.DefaultParameterNameDiscoverer}.
|
||||||
*/
|
*/
|
||||||
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
|
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
|
||||||
@@ -151,7 +154,7 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the {@link #setParameterNameDiscoverer(ParameterNameDiscoverer) configured}
|
* Return the {@link #setParameterNameDiscoverer configured}
|
||||||
* {@code ParameterNameDiscoverer}.
|
* {@code ParameterNameDiscoverer}.
|
||||||
*/
|
*/
|
||||||
public ParameterNameDiscoverer getParameterNameDiscoverer() {
|
public ParameterNameDiscoverer getParameterNameDiscoverer() {
|
||||||
@@ -159,12 +162,24 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure a resolver for the name of Object parameters with nested errors
|
* Configure a resolver to determine the name of an {@code @Valid} method
|
||||||
* to allow matching the name used in the higher level programming model,
|
* parameter to use for its {@link BindingResult}. This allows aligning with
|
||||||
* e.g. {@code @ModelAttribute} in Spring MVC.
|
* a higher level programming model such as to resolve the name of an
|
||||||
* <p>If not configured, {@link #createBindingResult} determines the name.
|
* {@code @ModelAttribute} method parameter in Spring MVC.
|
||||||
|
* <p>By default, the object name is resolved through:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link MethodParameter#getParameterName()} for input parameters
|
||||||
|
* <li>{@link Conventions#getVariableNameForReturnType(Method, Class, Object)}
|
||||||
|
* for a return type
|
||||||
|
* </ul>
|
||||||
|
* If a name cannot be determined, e.g. a return value with insufficient
|
||||||
|
* type information, then it defaults to one of:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code "{methodName}.arg{index}"} for input parameters
|
||||||
|
* <li>{@code "{methodName}.returnValue"} for a return type
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public void setBindingResultNameResolver(BindingResultNameResolver nameResolver) {
|
public void setObjectNameResolver(ObjectNameResolver nameResolver) {
|
||||||
this.objectNameResolver = nameResolver;
|
this.objectNameResolver = nameResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,11 +219,11 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||||||
invokeValidatorForArguments(target, method, arguments, groups);
|
invokeValidatorForArguments(target, method, arguments, groups);
|
||||||
|
|
||||||
if (violations.isEmpty()) {
|
if (violations.isEmpty()) {
|
||||||
return EMPTY_RESULT;
|
return emptyResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
return adaptViolations(target, method, violations,
|
return adaptViolations(target, method, violations,
|
||||||
i -> parameters != null ? parameters[i] : new MethodParameter(method, i),
|
i -> parameters != null ? parameters[i] : initMethodParameter(method, i),
|
||||||
i -> arguments[i]);
|
i -> arguments[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,11 +257,11 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||||||
invokeValidatorForReturnValue(target, method, returnValue, groups);
|
invokeValidatorForReturnValue(target, method, returnValue, groups);
|
||||||
|
|
||||||
if (violations.isEmpty()) {
|
if (violations.isEmpty()) {
|
||||||
return EMPTY_RESULT;
|
return emptyResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
return adaptViolations(target, method, violations,
|
return adaptViolations(target, method, violations,
|
||||||
i -> returnType != null ? returnType : new MethodParameter(method, -1),
|
i -> returnType != null ? returnType : initMethodParameter(method, -1),
|
||||||
i -> returnValue);
|
i -> returnValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +299,6 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
|
|
||||||
|
|
||||||
Object argument = argumentFunction.apply(parameter.getParameterIndex());
|
Object argument = argumentFunction.apply(parameter.getParameterIndex());
|
||||||
if (!itr.hasNext()) {
|
if (!itr.hasNext()) {
|
||||||
@@ -304,18 +318,17 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||||||
List<ParameterValidationResult> validatonResultList = new ArrayList<>();
|
List<ParameterValidationResult> validatonResultList = new ArrayList<>();
|
||||||
parameterViolations.forEach((parameter, builder) -> validatonResultList.add(builder.build()));
|
parameterViolations.forEach((parameter, builder) -> validatonResultList.add(builder.build()));
|
||||||
cascadedViolations.forEach((node, builder) -> validatonResultList.add(builder.build()));
|
cascadedViolations.forEach((node, builder) -> validatonResultList.add(builder.build()));
|
||||||
validatonResultList.sort(RESULT_COMPARATOR);
|
validatonResultList.sort(resultComparator);
|
||||||
|
|
||||||
return new DefaultMethodValidationResult(target, method, validatonResultList);
|
return new DefaultMethodValidationResult(target, method, validatonResultList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private MethodParameter initMethodParameter(Method method, int index) {
|
||||||
* Create a {@link MessageSourceResolvable} for the given violation.
|
MethodParameter parameter = new MethodParameter(method, index);
|
||||||
* @param target target of the method invocation to which validation was applied
|
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
|
||||||
* @param parameter the method parameter associated with the violation
|
return parameter;
|
||||||
* @param violation the violation
|
}
|
||||||
* @return the created {@code MessageSourceResolvable}
|
|
||||||
*/
|
|
||||||
private MessageSourceResolvable createMessageSourceResolvable(
|
private MessageSourceResolvable createMessageSourceResolvable(
|
||||||
Object target, MethodParameter parameter, ConstraintViolation<Object> violation) {
|
Object target, MethodParameter parameter, ConstraintViolation<Object> violation) {
|
||||||
|
|
||||||
@@ -331,47 +344,8 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||||||
return new DefaultMessageSourceResolvable(codes, arguments, violation.getMessage());
|
return new DefaultMessageSourceResolvable(codes, arguments, violation.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Select an object name and create a {@link BindingResult} for the argument.
|
|
||||||
* You can configure a {@link #setBindingResultNameResolver(BindingResultNameResolver)
|
|
||||||
* bindingResultNameResolver} to determine in a way that matches the specific
|
|
||||||
* programming model, e.g. {@code @ModelAttribute} or {@code @RequestBody} arguments
|
|
||||||
* in Spring MVC.
|
|
||||||
* <p>By default, the name is based on the parameter name, or for a return type on
|
|
||||||
* {@link Conventions#getVariableNameForReturnType(Method, Class, Object)}.
|
|
||||||
* <p>If a name cannot be determined for any reason, e.g. a return value with
|
|
||||||
* insufficient type information, then {@code "{methodName}.arg{index}"} is used.
|
|
||||||
* @param parameter the method parameter
|
|
||||||
* @param argument the argument value
|
|
||||||
* @return the determined name
|
|
||||||
*/
|
|
||||||
private BindingResult createBindingResult(MethodParameter parameter, @Nullable Object argument) {
|
private BindingResult createBindingResult(MethodParameter parameter, @Nullable Object argument) {
|
||||||
String objectName = null;
|
String objectName = this.objectNameResolver.resolveName(parameter, argument);
|
||||||
if (this.objectNameResolver != null) {
|
|
||||||
objectName = this.objectNameResolver.resolveName(parameter, argument);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (parameter.getParameterIndex() != -1) {
|
|
||||||
objectName = parameter.getParameterName();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
try {
|
|
||||||
Method method = parameter.getMethod();
|
|
||||||
if (method != null) {
|
|
||||||
Class<?> containingClass = parameter.getContainingClass();
|
|
||||||
Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, containingClass);
|
|
||||||
objectName = Conventions.getVariableNameForReturnType(method, resolvedType, argument);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IllegalArgumentException ex) {
|
|
||||||
// insufficient type information
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (objectName == null) {
|
|
||||||
int index = parameter.getParameterIndex();
|
|
||||||
objectName = (parameter.getExecutable().getName() + (index != -1 ? ".arg" + index : ""));
|
|
||||||
}
|
|
||||||
BeanPropertyBindingResult result = new BeanPropertyBindingResult(argument, objectName);
|
BeanPropertyBindingResult result = new BeanPropertyBindingResult(argument, objectName);
|
||||||
result.setMessageCodesResolver(this.messageCodesResolver);
|
result.setMessageCodesResolver(this.messageCodesResolver);
|
||||||
return result;
|
return result;
|
||||||
@@ -379,14 +353,15 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contract to determine the object name of an {@code @Valid} method parameter.
|
* Strategy to resolve the name of an {@code @Valid} method parameter to
|
||||||
|
* use for its {@link BindingResult}.
|
||||||
*/
|
*/
|
||||||
public interface BindingResultNameResolver {
|
public interface ObjectNameResolver {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the name for the given method parameter.
|
* Determine the name for the given method argument.
|
||||||
* @param parameter the method parameter
|
* @param parameter the method parameter
|
||||||
* @param value the argument or return value
|
* @param value the argument value or return value
|
||||||
* @return the name to use
|
* @return the name to use
|
||||||
*/
|
*/
|
||||||
String resolveName(MethodParameter parameter, @Nullable Object value);
|
String resolveName(MethodParameter parameter, @Nullable Object value);
|
||||||
@@ -484,6 +459,40 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default algorithm to select an object name, as described in
|
||||||
|
* {@link #setObjectNameResolver(ObjectNameResolver)}.
|
||||||
|
*/
|
||||||
|
private static class DefaultObjectNameResolver implements ObjectNameResolver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String resolveName(MethodParameter parameter, @Nullable Object value) {
|
||||||
|
String objectName = null;
|
||||||
|
if (parameter.getParameterIndex() != -1) {
|
||||||
|
objectName = parameter.getParameterName();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
Method method = parameter.getMethod();
|
||||||
|
if (method != null) {
|
||||||
|
Class<?> containingClass = parameter.getContainingClass();
|
||||||
|
Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, containingClass);
|
||||||
|
objectName = Conventions.getVariableNameForReturnType(method, resolvedType, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException ex) {
|
||||||
|
// insufficient type information
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (objectName == null) {
|
||||||
|
int index = parameter.getParameterIndex();
|
||||||
|
objectName = (parameter.getExecutable().getName() + (index != -1 ? ".arg" + index : ".returnValue"));
|
||||||
|
}
|
||||||
|
return objectName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Comparator for validation results, sorted by method parameter index first,
|
* Comparator for validation results, sorted by method parameter index first,
|
||||||
* also falling back on container indexes if necessary for cascaded
|
* also falling back on container indexes if necessary for cascaded
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public class MethodValidationAdapterTests {
|
|||||||
MyService target = new MyService();
|
MyService target = new MyService();
|
||||||
Method method = getMethod(target, "addStudent");
|
Method method = getMethod(target, "addStudent");
|
||||||
|
|
||||||
this.validationAdapter.setBindingResultNameResolver((parameter, value) -> "studentToAdd");
|
this.validationAdapter.setObjectNameResolver((param, value) -> "studentToAdd");
|
||||||
|
|
||||||
testArgs(target, method, new Object[] {faustino1234, new Person("Joe"), 1}, ex -> {
|
testArgs(target, method, new Object[] {faustino1234, new Person("Joe"), 1}, ex -> {
|
||||||
|
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
|
|||||||
import org.springframework.web.bind.support.WebBindingInitializer;
|
import org.springframework.web.bind.support.WebBindingInitializer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link org.springframework.validation.beanvalidation.MethodValidator} for
|
* {@link org.springframework.validation.beanvalidation.MethodValidator} that
|
||||||
* {@code @RequestMapping} methods.
|
* uses Bean Validation to validate {@code @RequestMapping} method arguments.
|
||||||
*
|
*
|
||||||
* <p>Handles validation results by populating {@link BindingResult} method
|
* <p>Handles validation results by populating {@link BindingResult} method
|
||||||
* arguments with errors from {@link MethodValidationResult#getBeanResults()
|
* arguments with errors from {@link MethodValidationResult#getBeanResults()
|
||||||
@@ -49,6 +49,9 @@ import org.springframework.web.bind.support.WebBindingInitializer;
|
|||||||
*/
|
*/
|
||||||
public final class HandlerMethodValidator implements MethodValidator {
|
public final class HandlerMethodValidator implements MethodValidator {
|
||||||
|
|
||||||
|
private static final MethodValidationAdapter.ObjectNameResolver objectNameResolver = new WebObjectNameResolver();
|
||||||
|
|
||||||
|
|
||||||
private final MethodValidationAdapter validationAdapter;
|
private final MethodValidationAdapter validationAdapter;
|
||||||
|
|
||||||
|
|
||||||
@@ -119,43 +122,51 @@ public final class HandlerMethodValidator implements MethodValidator {
|
|||||||
return this.validationAdapter.validateReturnValue(target, method, returnType, returnValue, groups);
|
return this.validationAdapter.validateReturnValue(target, method, returnType, returnValue, groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String determineObjectName(MethodParameter param, @Nullable Object argument) {
|
|
||||||
if (param.hasParameterAnnotation(RequestBody.class) || param.hasParameterAnnotation(RequestPart.class)) {
|
|
||||||
return Conventions.getVariableNameForParameter(param);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return (param.getParameterIndex() != -1 ?
|
|
||||||
ModelFactory.getNameForParameter(param) :
|
|
||||||
ModelFactory.getNameForReturnValue(argument, param));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static factory method to create a {@link HandlerMethodValidator} if Bean
|
* Static factory method to create a {@link HandlerMethodValidator} when Bean
|
||||||
* Validation is enabled in Spring MVC or WebFlux.
|
* Validation is enabled for use via {@link ConfigurableWebBindingInitializer},
|
||||||
|
* for example in Spring MVC or WebFlux config.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static MethodValidator from(
|
public static MethodValidator from(
|
||||||
@Nullable WebBindingInitializer bindingInitializer,
|
@Nullable WebBindingInitializer initializer, @Nullable ParameterNameDiscoverer paramNameDiscoverer) {
|
||||||
@Nullable ParameterNameDiscoverer parameterNameDiscoverer) {
|
|
||||||
|
|
||||||
if (bindingInitializer instanceof ConfigurableWebBindingInitializer configurableInitializer) {
|
if (initializer instanceof ConfigurableWebBindingInitializer configurableInitializer) {
|
||||||
if (configurableInitializer.getValidator() instanceof Validator validator) {
|
if (configurableInitializer.getValidator() instanceof Validator validator) {
|
||||||
MethodValidationAdapter adapter = new MethodValidationAdapter(validator);
|
MethodValidationAdapter adapter = new MethodValidationAdapter(validator);
|
||||||
if (parameterNameDiscoverer != null) {
|
if (paramNameDiscoverer != null) {
|
||||||
adapter.setParameterNameDiscoverer(parameterNameDiscoverer);
|
adapter.setParameterNameDiscoverer(paramNameDiscoverer);
|
||||||
}
|
}
|
||||||
MessageCodesResolver codesResolver = configurableInitializer.getMessageCodesResolver();
|
MessageCodesResolver codesResolver = configurableInitializer.getMessageCodesResolver();
|
||||||
if (codesResolver != null) {
|
if (codesResolver != null) {
|
||||||
adapter.setMessageCodesResolver(codesResolver);
|
adapter.setMessageCodesResolver(codesResolver);
|
||||||
}
|
}
|
||||||
HandlerMethodValidator methodValidator = new HandlerMethodValidator(adapter);
|
HandlerMethodValidator methodValidator = new HandlerMethodValidator(adapter);
|
||||||
adapter.setBindingResultNameResolver(HandlerMethodValidator::determineObjectName);
|
adapter.setObjectNameResolver(objectNameResolver);
|
||||||
return methodValidator;
|
return methodValidator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ObjectNameResolver for web controller methods.
|
||||||
|
*/
|
||||||
|
private static class WebObjectNameResolver implements MethodValidationAdapter.ObjectNameResolver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String resolveName(MethodParameter param, @Nullable Object value) {
|
||||||
|
if (param.hasParameterAnnotation(RequestBody.class) || param.hasParameterAnnotation(RequestPart.class)) {
|
||||||
|
return Conventions.getVariableNameForParameter(param);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (param.getParameterIndex() != -1 ?
|
||||||
|
ModelFactory.getNameForParameter(param) :
|
||||||
|
ModelFactory.getNameForReturnValue(value, param));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user