Skip to content

Commit e7cbe23

Browse files
dreis2211snicoll
authored andcommitted
Avoid exceptions when evaluating validation hints
Prior to this commit, evaluating validation hints for @javax.validation.Valid caused exceptions being raised when getting the value of this annotation, which does not exist. Bypassing AnnotationUtils.getValue() in those cases can improve performance by avoiding the cost incurred by raising exceptions. See gh-26787
1 parent d275a4e commit e7cbe23

File tree

5 files changed

+85
-50
lines changed

5 files changed

+85
-50
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2002-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.validation.annotation;
18+
19+
import java.lang.annotation.Annotation;
20+
21+
import org.springframework.core.annotation.AnnotationUtils;
22+
import org.springframework.lang.Nullable;
23+
24+
/**
25+
* Utility class for handling validation annotations.
26+
* Mainly for internal use within the framework.
27+
*
28+
* @author Christoph Dreis
29+
* @since 5.3.7
30+
*/
31+
public abstract class ValidationAnnotationUtils {
32+
33+
/**
34+
* Determine any validation hints by the given annotation.
35+
* <p>This implementation checks for {@code @javax.validation.Valid},
36+
* Spring's {@link org.springframework.validation.annotation.Validated},
37+
* and custom annotations whose name starts with "Valid".
38+
* @param ann the annotation (potentially a validation annotation)
39+
* @return the validation hints to apply (possibly an empty array),
40+
* or {@code null} if this annotation does not trigger any validation
41+
* @since 5.3.7
42+
*/
43+
@Nullable
44+
public static Object[] determineValidationHints(Annotation ann) {
45+
Class<? extends Annotation> annotationType = ann.annotationType();
46+
String annotationName = annotationType.getName();
47+
if ("javax.validation.Valid".equals(annotationName)) {
48+
return new Object[0];
49+
}
50+
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
51+
if (validatedAnn != null) {
52+
Object hints = validatedAnn.value();
53+
return convertValidationHints(hints);
54+
}
55+
if (annotationType.getSimpleName().startsWith("Valid")) {
56+
Object hints = AnnotationUtils.getValue(ann);
57+
return convertValidationHints(hints);
58+
}
59+
return null;
60+
}
61+
62+
private static Object[] convertValidationHints(@Nullable Object hints) {
63+
if (hints == null) {
64+
return new Object[0];
65+
}
66+
return (hints instanceof Object[] ? (Object[]) hints : new Object[]{hints});
67+
}
68+
69+
}

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

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -37,7 +37,6 @@
3737
import org.springframework.beans.BeanUtils;
3838
import org.springframework.beans.TypeMismatchException;
3939
import org.springframework.core.MethodParameter;
40-
import org.springframework.core.annotation.AnnotationUtils;
4140
import org.springframework.lang.Nullable;
4241
import org.springframework.util.Assert;
4342
import org.springframework.util.StringUtils;
@@ -46,7 +45,7 @@
4645
import org.springframework.validation.Errors;
4746
import org.springframework.validation.SmartValidator;
4847
import org.springframework.validation.Validator;
49-
import org.springframework.validation.annotation.Validated;
48+
import org.springframework.validation.annotation.ValidationAnnotationUtils;
5049
import org.springframework.web.bind.WebDataBinder;
5150
import org.springframework.web.bind.annotation.ModelAttribute;
5251
import org.springframework.web.bind.support.WebDataBinderFactory;
@@ -362,7 +361,7 @@ else if (StringUtils.startsWithIgnoreCase(request.getHeader("Content-Type"), "mu
362361
*/
363362
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
364363
for (Annotation ann : parameter.getParameterAnnotations()) {
365-
Object[] validationHints = determineValidationHints(ann);
364+
Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
366365
if (validationHints != null) {
367366
binder.validate(validationHints);
368367
break;
@@ -388,7 +387,7 @@ protected void validateValueIfApplicable(WebDataBinder binder, MethodParameter p
388387
Class<?> targetType, String fieldName, @Nullable Object value) {
389388

390389
for (Annotation ann : parameter.getParameterAnnotations()) {
391-
Object[] validationHints = determineValidationHints(ann);
390+
Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
392391
if (validationHints != null) {
393392
for (Validator validator : binder.getValidators()) {
394393
if (validator instanceof SmartValidator) {
@@ -406,26 +405,6 @@ protected void validateValueIfApplicable(WebDataBinder binder, MethodParameter p
406405
}
407406
}
408407

409-
/**
410-
* Determine any validation triggered by the given annotation.
411-
* @param ann the annotation (potentially a validation annotation)
412-
* @return the validation hints to apply (possibly an empty array),
413-
* or {@code null} if this annotation does not trigger any validation
414-
* @since 5.1
415-
*/
416-
@Nullable
417-
private Object[] determineValidationHints(Annotation ann) {
418-
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
419-
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
420-
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
421-
if (hints == null) {
422-
return new Object[0];
423-
}
424-
return (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
425-
}
426-
return null;
427-
}
428-
429408
/**
430409
* Whether to raise a fatal bind exception on validation errors.
431410
* <p>The default implementation delegates to {@link #isBindExceptionRequired(MethodParameter)}.

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import org.springframework.core.ReactiveAdapter;
3232
import org.springframework.core.ReactiveAdapterRegistry;
3333
import org.springframework.core.ResolvableType;
34-
import org.springframework.core.annotation.AnnotationUtils;
3534
import org.springframework.core.codec.DecodingException;
3635
import org.springframework.core.codec.Hints;
3736
import org.springframework.core.io.buffer.DataBuffer;
@@ -45,7 +44,7 @@
4544
import org.springframework.lang.Nullable;
4645
import org.springframework.util.Assert;
4746
import org.springframework.validation.Validator;
48-
import org.springframework.validation.annotation.Validated;
47+
import org.springframework.validation.annotation.ValidationAnnotationUtils;
4948
import org.springframework.web.bind.support.WebExchangeBindException;
5049
import org.springframework.web.bind.support.WebExchangeDataBinder;
5150
import org.springframework.web.reactive.BindingContext;
@@ -240,10 +239,9 @@ private ServerWebInputException handleMissingBody(MethodParameter parameter) {
240239
private Object[] extractValidationHints(MethodParameter parameter) {
241240
Annotation[] annotations = parameter.getParameterAnnotations();
242241
for (Annotation ann : annotations) {
243-
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
244-
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
245-
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
246-
return (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
242+
Object[] hints = ValidationAnnotationUtils.determineValidationHints(ann);
243+
if (hints != null) {
244+
return hints;
247245
}
248246
}
249247
return null;

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

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,14 +30,13 @@
3030
import org.springframework.core.ReactiveAdapter;
3131
import org.springframework.core.ReactiveAdapterRegistry;
3232
import org.springframework.core.ResolvableType;
33-
import org.springframework.core.annotation.AnnotationUtils;
3433
import org.springframework.lang.Nullable;
3534
import org.springframework.ui.Model;
3635
import org.springframework.util.Assert;
3736
import org.springframework.util.ClassUtils;
3837
import org.springframework.validation.BindingResult;
3938
import org.springframework.validation.Errors;
40-
import org.springframework.validation.annotation.Validated;
39+
import org.springframework.validation.annotation.ValidationAnnotationUtils;
4140
import org.springframework.web.bind.annotation.ModelAttribute;
4241
import org.springframework.web.bind.support.WebExchangeBindException;
4342
import org.springframework.web.bind.support.WebExchangeDataBinder;
@@ -270,16 +269,9 @@ private boolean hasErrorsArgument(MethodParameter parameter) {
270269

271270
private void validateIfApplicable(WebExchangeDataBinder binder, MethodParameter parameter) {
272271
for (Annotation ann : parameter.getParameterAnnotations()) {
273-
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
274-
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
275-
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
276-
if (hints != null) {
277-
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
278-
binder.validate(validationHints);
279-
}
280-
else {
281-
binder.validate();
282-
}
272+
Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
273+
if (validationHints != null) {
274+
binder.validate(validationHints);
283275
}
284276
}
285277
}

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636

3737
import org.springframework.core.MethodParameter;
3838
import org.springframework.core.ResolvableType;
39-
import org.springframework.core.annotation.AnnotationUtils;
4039
import org.springframework.core.log.LogFormatUtils;
4140
import org.springframework.http.HttpHeaders;
4241
import org.springframework.http.HttpInputMessage;
@@ -52,7 +51,7 @@
5251
import org.springframework.util.Assert;
5352
import org.springframework.util.StreamUtils;
5453
import org.springframework.validation.Errors;
55-
import org.springframework.validation.annotation.Validated;
54+
import org.springframework.validation.annotation.ValidationAnnotationUtils;
5655
import org.springframework.web.HttpMediaTypeNotSupportedException;
5756
import org.springframework.web.bind.WebDataBinder;
5857
import org.springframework.web.context.request.NativeWebRequest;
@@ -241,10 +240,8 @@ protected ServletServerHttpRequest createInputMessage(NativeWebRequest webReques
241240
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
242241
Annotation[] annotations = parameter.getParameterAnnotations();
243242
for (Annotation ann : annotations) {
244-
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
245-
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
246-
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
247-
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
243+
Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
244+
if (validationHints != null) {
248245
binder.validate(validationHints);
249246
break;
250247
}

0 commit comments

Comments
 (0)