70
70
*/
71
71
public class MethodValidationAdapter implements MethodValidator {
72
72
73
- private static final Comparator < ParameterValidationResult > RESULT_COMPARATOR = new ResultComparator ();
73
+ private static final ObjectNameResolver defaultObjectNameResolver = new DefaultObjectNameResolver ();
74
74
75
- private static final MethodValidationResult EMPTY_RESULT = new EmptyMethodValidationResult ();
75
+ private static final Comparator <ParameterValidationResult > resultComparator = new ResultComparator ();
76
+
77
+ private static final MethodValidationResult emptyResult = new EmptyMethodValidationResult ();
76
78
77
79
78
80
private final Supplier <Validator > validator ;
@@ -83,8 +85,7 @@ public class MethodValidationAdapter implements MethodValidator {
83
85
84
86
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer ();
85
87
86
- @ Nullable
87
- private BindingResultNameResolver objectNameResolver ;
88
+ private ObjectNameResolver objectNameResolver = defaultObjectNameResolver ;
88
89
89
90
90
91
/**
@@ -142,29 +143,43 @@ public MessageCodesResolver getMessageCodesResolver() {
142
143
}
143
144
144
145
/**
145
- * Set the ParameterNameDiscoverer to use to resolve method parameter names
146
- * that is in turn used to create error codes for {@link MessageSourceResolvable}.
146
+ * Set the {@code ParameterNameDiscoverer} to discover method parameter names
147
+ * with to create error codes for {@link MessageSourceResolvable}. Used only
148
+ * when {@link MethodParameter}s are not passed into
149
+ * {@link #validateArguments} or {@link #validateReturnValue}.
147
150
* <p>Default is {@link org.springframework.core.DefaultParameterNameDiscoverer}.
148
151
*/
149
152
public void setParameterNameDiscoverer (ParameterNameDiscoverer parameterNameDiscoverer ) {
150
153
this .parameterNameDiscoverer = parameterNameDiscoverer ;
151
154
}
152
155
153
156
/**
154
- * Return the {@link #setParameterNameDiscoverer(ParameterNameDiscoverer) configured}
157
+ * Return the {@link #setParameterNameDiscoverer configured}
155
158
* {@code ParameterNameDiscoverer}.
156
159
*/
157
160
public ParameterNameDiscoverer getParameterNameDiscoverer () {
158
161
return this .parameterNameDiscoverer ;
159
162
}
160
163
161
164
/**
162
- * Configure a resolver for the name of Object parameters with nested errors
163
- * to allow matching the name used in the higher level programming model,
164
- * e.g. {@code @ModelAttribute} in Spring MVC.
165
- * <p>If not configured, {@link #createBindingResult} determines the name.
165
+ * Configure a resolver to determine the name of an {@code @Valid} method
166
+ * parameter to use for its {@link BindingResult}. This allows aligning with
167
+ * a higher level programming model such as to resolve the name of an
168
+ * {@code @ModelAttribute} method parameter in Spring MVC.
169
+ * <p>By default, the object name is resolved through:
170
+ * <ul>
171
+ * <li>{@link MethodParameter#getParameterName()} for input parameters
172
+ * <li>{@link Conventions#getVariableNameForReturnType(Method, Class, Object)}
173
+ * for a return type
174
+ * </ul>
175
+ * If a name cannot be determined, e.g. a return value with insufficient
176
+ * type information, then it defaults to one of:
177
+ * <ul>
178
+ * <li>{@code "{methodName}.arg{index}"} for input parameters
179
+ * <li>{@code "{methodName}.returnValue"} for a return type
180
+ * </ul>
166
181
*/
167
- public void setBindingResultNameResolver ( BindingResultNameResolver nameResolver ) {
182
+ public void setObjectNameResolver ( ObjectNameResolver nameResolver ) {
168
183
this .objectNameResolver = nameResolver ;
169
184
}
170
185
@@ -204,11 +219,11 @@ public final MethodValidationResult validateArguments(
204
219
invokeValidatorForArguments (target , method , arguments , groups );
205
220
206
221
if (violations .isEmpty ()) {
207
- return EMPTY_RESULT ;
222
+ return emptyResult ;
208
223
}
209
224
210
225
return adaptViolations (target , method , violations ,
211
- i -> parameters != null ? parameters [i ] : new MethodParameter (method , i ),
226
+ i -> parameters != null ? parameters [i ] : initMethodParameter (method , i ),
212
227
i -> arguments [i ]);
213
228
}
214
229
@@ -242,11 +257,11 @@ public final MethodValidationResult validateReturnValue(
242
257
invokeValidatorForReturnValue (target , method , returnValue , groups );
243
258
244
259
if (violations .isEmpty ()) {
245
- return EMPTY_RESULT ;
260
+ return emptyResult ;
246
261
}
247
262
248
263
return adaptViolations (target , method , violations ,
249
- i -> returnType != null ? returnType : new MethodParameter (method , -1 ),
264
+ i -> returnType != null ? returnType : initMethodParameter (method , -1 ),
250
265
i -> returnValue );
251
266
}
252
267
@@ -284,7 +299,6 @@ else if (node.getKind().equals(ElementKind.RETURN_VALUE)) {
284
299
else {
285
300
continue ;
286
301
}
287
- parameter .initParameterNameDiscovery (this .parameterNameDiscoverer );
288
302
289
303
Object argument = argumentFunction .apply (parameter .getParameterIndex ());
290
304
if (!itr .hasNext ()) {
@@ -304,18 +318,17 @@ else if (node.getKind().equals(ElementKind.RETURN_VALUE)) {
304
318
List <ParameterValidationResult > validatonResultList = new ArrayList <>();
305
319
parameterViolations .forEach ((parameter , builder ) -> validatonResultList .add (builder .build ()));
306
320
cascadedViolations .forEach ((node , builder ) -> validatonResultList .add (builder .build ()));
307
- validatonResultList .sort (RESULT_COMPARATOR );
321
+ validatonResultList .sort (resultComparator );
308
322
309
323
return new DefaultMethodValidationResult (target , method , validatonResultList );
310
324
}
311
325
312
- /**
313
- * Create a {@link MessageSourceResolvable} for the given violation.
314
- * @param target target of the method invocation to which validation was applied
315
- * @param parameter the method parameter associated with the violation
316
- * @param violation the violation
317
- * @return the created {@code MessageSourceResolvable}
318
- */
326
+ private MethodParameter initMethodParameter (Method method , int index ) {
327
+ MethodParameter parameter = new MethodParameter (method , index );
328
+ parameter .initParameterNameDiscovery (this .parameterNameDiscoverer );
329
+ return parameter ;
330
+ }
331
+
319
332
private MessageSourceResolvable createMessageSourceResolvable (
320
333
Object target , MethodParameter parameter , ConstraintViolation <Object > violation ) {
321
334
@@ -331,62 +344,24 @@ private MessageSourceResolvable createMessageSourceResolvable(
331
344
return new DefaultMessageSourceResolvable (codes , arguments , violation .getMessage ());
332
345
}
333
346
334
- /**
335
- * Select an object name and create a {@link BindingResult} for the argument.
336
- * You can configure a {@link #setBindingResultNameResolver(BindingResultNameResolver)
337
- * bindingResultNameResolver} to determine in a way that matches the specific
338
- * programming model, e.g. {@code @ModelAttribute} or {@code @RequestBody} arguments
339
- * in Spring MVC.
340
- * <p>By default, the name is based on the parameter name, or for a return type on
341
- * {@link Conventions#getVariableNameForReturnType(Method, Class, Object)}.
342
- * <p>If a name cannot be determined for any reason, e.g. a return value with
343
- * insufficient type information, then {@code "{methodName}.arg{index}"} is used.
344
- * @param parameter the method parameter
345
- * @param argument the argument value
346
- * @return the determined name
347
- */
348
347
private BindingResult createBindingResult (MethodParameter parameter , @ Nullable Object argument ) {
349
- String objectName = null ;
350
- if (this .objectNameResolver != null ) {
351
- objectName = this .objectNameResolver .resolveName (parameter , argument );
352
- }
353
- else {
354
- if (parameter .getParameterIndex () != -1 ) {
355
- objectName = parameter .getParameterName ();
356
- }
357
- else {
358
- try {
359
- Method method = parameter .getMethod ();
360
- if (method != null ) {
361
- Class <?> containingClass = parameter .getContainingClass ();
362
- Class <?> resolvedType = GenericTypeResolver .resolveReturnType (method , containingClass );
363
- objectName = Conventions .getVariableNameForReturnType (method , resolvedType , argument );
364
- }
365
- }
366
- catch (IllegalArgumentException ex ) {
367
- // insufficient type information
368
- }
369
- }
370
- }
371
- if (objectName == null ) {
372
- int index = parameter .getParameterIndex ();
373
- objectName = (parameter .getExecutable ().getName () + (index != -1 ? ".arg" + index : "" ));
374
- }
348
+ String objectName = this .objectNameResolver .resolveName (parameter , argument );
375
349
BeanPropertyBindingResult result = new BeanPropertyBindingResult (argument , objectName );
376
350
result .setMessageCodesResolver (this .messageCodesResolver );
377
351
return result ;
378
352
}
379
353
380
354
381
355
/**
382
- * Contract to determine the object name of an {@code @Valid} method parameter.
356
+ * Strategy to resolve the name of an {@code @Valid} method parameter to
357
+ * use for its {@link BindingResult}.
383
358
*/
384
- public interface BindingResultNameResolver {
359
+ public interface ObjectNameResolver {
385
360
386
361
/**
387
- * Determine the name for the given method parameter .
362
+ * Determine the name for the given method argument .
388
363
* @param parameter the method parameter
389
- * @param value the argument or return value
364
+ * @param value the argument value or return value
390
365
* @return the name to use
391
366
*/
392
367
String resolveName (MethodParameter parameter , @ Nullable Object value );
@@ -484,6 +459,40 @@ public ParameterErrors build() {
484
459
}
485
460
486
461
462
+ /**
463
+ * Default algorithm to select an object name, as described in
464
+ * {@link #setObjectNameResolver(ObjectNameResolver)}.
465
+ */
466
+ private static class DefaultObjectNameResolver implements ObjectNameResolver {
467
+
468
+ @ Override
469
+ public String resolveName (MethodParameter parameter , @ Nullable Object value ) {
470
+ String objectName = null ;
471
+ if (parameter .getParameterIndex () != -1 ) {
472
+ objectName = parameter .getParameterName ();
473
+ }
474
+ else {
475
+ try {
476
+ Method method = parameter .getMethod ();
477
+ if (method != null ) {
478
+ Class <?> containingClass = parameter .getContainingClass ();
479
+ Class <?> resolvedType = GenericTypeResolver .resolveReturnType (method , containingClass );
480
+ objectName = Conventions .getVariableNameForReturnType (method , resolvedType , value );
481
+ }
482
+ }
483
+ catch (IllegalArgumentException ex ) {
484
+ // insufficient type information
485
+ }
486
+ }
487
+ if (objectName == null ) {
488
+ int index = parameter .getParameterIndex ();
489
+ objectName = (parameter .getExecutable ().getName () + (index != -1 ? ".arg" + index : ".returnValue" ));
490
+ }
491
+ return objectName ;
492
+ }
493
+ }
494
+
495
+
487
496
/**
488
497
* Comparator for validation results, sorted by method parameter index first,
489
498
* also falling back on container indexes if necessary for cascaded
0 commit comments