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:
parent
7a79da589a
commit
84e863f803
|
|
@ -70,9 +70,11 @@ import org.springframework.validation.annotation.Validated;
|
|||
*/
|
||||
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;
|
||||
|
|
@ -83,8 +85,7 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||
|
||||
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
||||
|
||||
@Nullable
|
||||
private BindingResultNameResolver objectNameResolver;
|
||||
private ObjectNameResolver objectNameResolver = defaultObjectNameResolver;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -142,8 +143,10 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set the ParameterNameDiscoverer to use to resolve method parameter names
|
||||
* that is in turn used to create error codes for {@link MessageSourceResolvable}.
|
||||
* Set the {@code ParameterNameDiscoverer} to discover method parameter names
|
||||
* 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}.
|
||||
*/
|
||||
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}.
|
||||
*/
|
||||
public ParameterNameDiscoverer getParameterNameDiscoverer() {
|
||||
|
|
@ -159,12 +162,24 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||
}
|
||||
|
||||
/**
|
||||
* Configure a resolver for the name of Object parameters with nested errors
|
||||
* to allow matching the name used in the higher level programming model,
|
||||
* e.g. {@code @ModelAttribute} in Spring MVC.
|
||||
* <p>If not configured, {@link #createBindingResult} determines the name.
|
||||
* Configure a resolver to determine the name of an {@code @Valid} method
|
||||
* parameter to use for its {@link BindingResult}. This allows aligning with
|
||||
* a higher level programming model such as to resolve the name of an
|
||||
* {@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;
|
||||
}
|
||||
|
||||
|
|
@ -204,11 +219,11 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||
invokeValidatorForArguments(target, method, arguments, groups);
|
||||
|
||||
if (violations.isEmpty()) {
|
||||
return EMPTY_RESULT;
|
||||
return emptyResult;
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
|
|
@ -242,11 +257,11 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||
invokeValidatorForReturnValue(target, method, returnValue, groups);
|
||||
|
||||
if (violations.isEmpty()) {
|
||||
return EMPTY_RESULT;
|
||||
return emptyResult;
|
||||
}
|
||||
|
||||
return adaptViolations(target, method, violations,
|
||||
i -> returnType != null ? returnType : new MethodParameter(method, -1),
|
||||
i -> returnType != null ? returnType : initMethodParameter(method, -1),
|
||||
i -> returnValue);
|
||||
}
|
||||
|
||||
|
|
@ -284,7 +299,6 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||
else {
|
||||
continue;
|
||||
}
|
||||
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
|
||||
|
||||
Object argument = argumentFunction.apply(parameter.getParameterIndex());
|
||||
if (!itr.hasNext()) {
|
||||
|
|
@ -304,18 +318,17 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||
List<ParameterValidationResult> validatonResultList = new ArrayList<>();
|
||||
parameterViolations.forEach((parameter, 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link MessageSourceResolvable} for the given violation.
|
||||
* @param target target of the method invocation to which validation was applied
|
||||
* @param parameter the method parameter associated with the violation
|
||||
* @param violation the violation
|
||||
* @return the created {@code MessageSourceResolvable}
|
||||
*/
|
||||
private MethodParameter initMethodParameter(Method method, int index) {
|
||||
MethodParameter parameter = new MethodParameter(method, index);
|
||||
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
|
||||
return parameter;
|
||||
}
|
||||
|
||||
private MessageSourceResolvable createMessageSourceResolvable(
|
||||
Object target, MethodParameter parameter, ConstraintViolation<Object> violation) {
|
||||
|
||||
|
|
@ -331,47 +344,8 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||
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) {
|
||||
String objectName = null;
|
||||
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 : ""));
|
||||
}
|
||||
String objectName = this.objectNameResolver.resolveName(parameter, argument);
|
||||
BeanPropertyBindingResult result = new BeanPropertyBindingResult(argument, objectName);
|
||||
result.setMessageCodesResolver(this.messageCodesResolver);
|
||||
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 value the argument or return value
|
||||
* @param value the argument value or return value
|
||||
* @return the name to use
|
||||
*/
|
||||
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,
|
||||
* also falling back on container indexes if necessary for cascaded
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ public class MethodValidationAdapterTests {
|
|||
MyService target = new MyService();
|
||||
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 -> {
|
||||
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
|
|||
import org.springframework.web.bind.support.WebBindingInitializer;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.validation.beanvalidation.MethodValidator} for
|
||||
* {@code @RequestMapping} methods.
|
||||
* {@link org.springframework.validation.beanvalidation.MethodValidator} that
|
||||
* uses Bean Validation to validate {@code @RequestMapping} method arguments.
|
||||
*
|
||||
* <p>Handles validation results by populating {@link BindingResult} method
|
||||
* arguments with errors from {@link MethodValidationResult#getBeanResults()
|
||||
|
|
@ -49,6 +49,9 @@ import org.springframework.web.bind.support.WebBindingInitializer;
|
|||
*/
|
||||
public final class HandlerMethodValidator implements MethodValidator {
|
||||
|
||||
private static final MethodValidationAdapter.ObjectNameResolver objectNameResolver = new WebObjectNameResolver();
|
||||
|
||||
|
||||
private final MethodValidationAdapter validationAdapter;
|
||||
|
||||
|
||||
|
|
@ -119,43 +122,51 @@ public final class HandlerMethodValidator implements MethodValidator {
|
|||
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
|
||||
* Validation is enabled in Spring MVC or WebFlux.
|
||||
* Static factory method to create a {@link HandlerMethodValidator} when Bean
|
||||
* Validation is enabled for use via {@link ConfigurableWebBindingInitializer},
|
||||
* for example in Spring MVC or WebFlux config.
|
||||
*/
|
||||
@Nullable
|
||||
public static MethodValidator from(
|
||||
@Nullable WebBindingInitializer bindingInitializer,
|
||||
@Nullable ParameterNameDiscoverer parameterNameDiscoverer) {
|
||||
@Nullable WebBindingInitializer initializer, @Nullable ParameterNameDiscoverer paramNameDiscoverer) {
|
||||
|
||||
if (bindingInitializer instanceof ConfigurableWebBindingInitializer configurableInitializer) {
|
||||
if (initializer instanceof ConfigurableWebBindingInitializer configurableInitializer) {
|
||||
if (configurableInitializer.getValidator() instanceof Validator validator) {
|
||||
MethodValidationAdapter adapter = new MethodValidationAdapter(validator);
|
||||
if (parameterNameDiscoverer != null) {
|
||||
adapter.setParameterNameDiscoverer(parameterNameDiscoverer);
|
||||
if (paramNameDiscoverer != null) {
|
||||
adapter.setParameterNameDiscoverer(paramNameDiscoverer);
|
||||
}
|
||||
MessageCodesResolver codesResolver = configurableInitializer.getMessageCodesResolver();
|
||||
if (codesResolver != null) {
|
||||
adapter.setMessageCodesResolver(codesResolver);
|
||||
}
|
||||
HandlerMethodValidator methodValidator = new HandlerMethodValidator(adapter);
|
||||
adapter.setBindingResultNameResolver(HandlerMethodValidator::determineObjectName);
|
||||
adapter.setObjectNameResolver(objectNameResolver);
|
||||
return methodValidator;
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue