Skip to content

Commit dd0d26b

Browse files
committed
Refine exception handling for type not present versus access exception
Includes TypeVariable bypass for reflection-free annotation retrieval. Includes info log message for annotation attribute retrieval failure. Closes gh-27182 (cherry picked from commit 70247c4)
1 parent d074f66 commit dd0d26b

File tree

7 files changed

+417
-472
lines changed

7 files changed

+417
-472
lines changed

spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -214,7 +214,12 @@ else if (Type[].class == method.getReturnType() && ObjectUtils.isEmpty(args)) {
214214
return result;
215215
}
216216

217-
return ReflectionUtils.invokeMethod(method, this.provider.getType(), args);
217+
Type type = this.provider.getType();
218+
if (type instanceof TypeVariable<?> tv && method.getName().equals("getName")) {
219+
// Avoid reflection for common comparison of type variables
220+
return tv.getName();
221+
}
222+
return ReflectionUtils.invokeMethod(method, type, args);
218223
}
219224
}
220225

spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -757,7 +757,7 @@ public static boolean isInJavaLangAnnotationPackage(@Nullable String annotationT
757757
* Google App Engine's late arrival of {@code TypeNotPresentExceptionProxy} for
758758
* {@code Class} values (instead of early {@code Class.getAnnotations() failure}).
759759
* <p>This method not failing indicates that {@link #getAnnotationAttributes(Annotation)}
760-
* won't failure either (when attempted later on).
760+
* won't fail either (when attempted later on).
761761
* @param annotation the annotation to validate
762762
* @throws IllegalStateException if a declared {@code Class} attribute could not be read
763763
* @since 4.3.15
@@ -1056,8 +1056,7 @@ public static Object getValue(@Nullable Annotation annotation, @Nullable String
10561056
return null;
10571057
}
10581058
catch (Throwable ex) {
1059-
rethrowAnnotationConfigurationException(ex);
1060-
handleIntrospectionFailure(annotation.getClass(), ex);
1059+
handleValueRetrievalFailure(annotation, ex);
10611060
return null;
10621061
}
10631062
}
@@ -1073,14 +1072,18 @@ public static Object getValue(@Nullable Annotation annotation, @Nullable String
10731072
* @return the value returned from the method invocation
10741073
* @since 5.3.24
10751074
*/
1076-
static Object invokeAnnotationMethod(Method method, Object annotation) {
1075+
@Nullable
1076+
static Object invokeAnnotationMethod(Method method, @Nullable Object annotation) {
1077+
if (annotation == null) {
1078+
return null;
1079+
}
10771080
if (Proxy.isProxyClass(annotation.getClass())) {
10781081
try {
10791082
InvocationHandler handler = Proxy.getInvocationHandler(annotation);
10801083
return handler.invoke(annotation, method, null);
10811084
}
10821085
catch (Throwable ex) {
1083-
// ignore and fall back to reflection below
1086+
// Ignore and fall back to reflection below
10841087
}
10851088
}
10861089
return ReflectionUtils.invokeMethod(method, annotation);
@@ -1114,20 +1117,32 @@ static void rethrowAnnotationConfigurationException(Throwable ex) {
11141117
* @see #rethrowAnnotationConfigurationException
11151118
* @see IntrospectionFailureLogger
11161119
*/
1117-
static void handleIntrospectionFailure(@Nullable AnnotatedElement element, Throwable ex) {
1120+
static void handleIntrospectionFailure(AnnotatedElement element, Throwable ex) {
11181121
rethrowAnnotationConfigurationException(ex);
11191122
IntrospectionFailureLogger logger = IntrospectionFailureLogger.INFO;
11201123
boolean meta = false;
11211124
if (element instanceof Class<?> clazz && Annotation.class.isAssignableFrom(clazz)) {
1122-
// Meta-annotation or (default) value lookup on an annotation type
1125+
// Meta-annotation introspection failure
11231126
logger = IntrospectionFailureLogger.DEBUG;
11241127
meta = true;
11251128
}
11261129
if (logger.isEnabled()) {
1127-
String message = meta ?
1128-
"Failed to meta-introspect annotation " :
1129-
"Failed to introspect annotations on ";
1130-
logger.log(message + element + ": " + ex);
1130+
logger.log("Failed to " + (meta ? "meta-introspect annotation " : "introspect annotations on ") +
1131+
element + ": " + ex);
1132+
}
1133+
}
1134+
1135+
/**
1136+
* Handle the supplied value retrieval exception.
1137+
* @param annotation the annotation instance from which to retrieve the value
1138+
* @param ex the exception that we encountered
1139+
* @see #handleIntrospectionFailure
1140+
*/
1141+
private static void handleValueRetrievalFailure(Annotation annotation, Throwable ex) {
1142+
rethrowAnnotationConfigurationException(ex);
1143+
IntrospectionFailureLogger logger = IntrospectionFailureLogger.INFO;
1144+
if (logger.isEnabled()) {
1145+
logger.log("Failed to retrieve value from " + annotation + ": " + ex);
11311146
}
11321147
}
11331148

spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -453,7 +453,7 @@ static Annotation[] getDeclaredAnnotations(AnnotatedElement source, boolean defe
453453
for (int i = 0; i < annotations.length; i++) {
454454
Annotation annotation = annotations[i];
455455
if (isIgnorable(annotation.annotationType()) ||
456-
!AttributeMethods.forAnnotationType(annotation.annotationType()).isValid(annotation)) {
456+
!AttributeMethods.forAnnotationType(annotation.annotationType()).canLoad(annotation)) {
457457
annotations[i] = null;
458458
}
459459
else {

spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -45,7 +45,7 @@ final class AttributeMethods {
4545
if (m1 != null && m2 != null) {
4646
return m1.getName().compareTo(m2.getName());
4747
}
48-
return m1 != null ? -1 : 1;
48+
return (m1 != null ? -1 : 1);
4949
};
5050

5151

@@ -87,18 +87,26 @@ private AttributeMethods(@Nullable Class<? extends Annotation> annotationType, M
8787
/**
8888
* Determine if values from the given annotation can be safely accessed without
8989
* causing any {@link TypeNotPresentException TypeNotPresentExceptions}.
90+
* <p>This method is designed to cover Google App Engine's late arrival of such
91+
* exceptions for {@code Class} values (instead of the more typical early
92+
* {@code Class.getAnnotations() failure} on a regular JVM).
9093
* @param annotation the annotation to check
9194
* @return {@code true} if all values are present
9295
* @see #validate(Annotation)
9396
*/
94-
boolean isValid(Annotation annotation) {
97+
boolean canLoad(Annotation annotation) {
9598
assertAnnotation(annotation);
9699
for (int i = 0; i < size(); i++) {
97100
if (canThrowTypeNotPresentException(i)) {
98101
try {
99102
AnnotationUtils.invokeAnnotationMethod(get(i), annotation);
100103
}
104+
catch (IllegalStateException ex) {
105+
// Plain invocation failure to expose -> leave up to attribute retrieval
106+
// (if any) where such invocation failure will be logged eventually.
107+
}
101108
catch (Throwable ex) {
109+
// TypeNotPresentException etc. -> annotation type not actually loadable.
102110
return false;
103111
}
104112
}
@@ -108,13 +116,13 @@ boolean isValid(Annotation annotation) {
108116

109117
/**
110118
* Check if values from the given annotation can be safely accessed without causing
111-
* any {@link TypeNotPresentException TypeNotPresentExceptions}. In particular,
112-
* this method is designed to cover Google App Engine's late arrival of such
119+
* any {@link TypeNotPresentException TypeNotPresentExceptions}.
120+
* <p>This method is designed to cover Google App Engine's late arrival of such
113121
* exceptions for {@code Class} values (instead of the more typical early
114-
* {@code Class.getAnnotations() failure}).
122+
* {@code Class.getAnnotations() failure} on a regular JVM).
115123
* @param annotation the annotation to validate
116124
* @throws IllegalStateException if a declared {@code Class} attribute could not be read
117-
* @see #isValid(Annotation)
125+
* @see #canLoad(Annotation)
118126
*/
119127
void validate(Annotation annotation) {
120128
assertAnnotation(annotation);
@@ -123,6 +131,9 @@ void validate(Annotation annotation) {
123131
try {
124132
AnnotationUtils.invokeAnnotationMethod(get(i), annotation);
125133
}
134+
catch (IllegalStateException ex) {
135+
throw ex;
136+
}
126137
catch (Throwable ex) {
127138
throw new IllegalStateException("Could not obtain annotation attribute value for " +
128139
get(i).getName() + " declared on " + annotation.annotationType(), ex);
@@ -147,7 +158,7 @@ private void assertAnnotation(Annotation annotation) {
147158
@Nullable
148159
Method get(String name) {
149160
int index = indexOf(name);
150-
return index != -1 ? this.attributeMethods[index] : null;
161+
return (index != -1 ? this.attributeMethods[index] : null);
151162
}
152163

153164
/**

spring-core/src/test/java/org/springframework/core/annotation/AnnotationIntrospectionFailureTests.java

Lines changed: 33 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -43,55 +43,45 @@ class AnnotationIntrospectionFailureTests {
4343

4444
@Test
4545
void filteredTypeThrowsTypeNotPresentException() throws Exception {
46-
FilteringClassLoader classLoader = new FilteringClassLoader(
47-
getClass().getClassLoader());
48-
Class<?> withExampleAnnotation = ClassUtils.forName(
49-
WithExampleAnnotation.class.getName(), classLoader);
50-
Annotation annotation = withExampleAnnotation.getAnnotations()[0];
46+
FilteringClassLoader classLoader = new FilteringClassLoader(getClass().getClassLoader());
47+
Class<?> withAnnotation = ClassUtils.forName(WithExampleAnnotation.class.getName(), classLoader);
48+
Annotation annotation = withAnnotation.getAnnotations()[0];
5149
Method method = annotation.annotationType().getMethod("value");
5250
method.setAccessible(true);
53-
assertThatExceptionOfType(TypeNotPresentException.class).isThrownBy(() ->
54-
ReflectionUtils.invokeMethod(method, annotation))
55-
.withCauseInstanceOf(ClassNotFoundException.class);
51+
assertThatExceptionOfType(TypeNotPresentException.class)
52+
.isThrownBy(() -> ReflectionUtils.invokeMethod(method, annotation))
53+
.withCauseInstanceOf(ClassNotFoundException.class);
5654
}
5755

5856
@Test
5957
@SuppressWarnings("unchecked")
6058
void filteredTypeInMetaAnnotationWhenUsingAnnotatedElementUtilsHandlesException() throws Exception {
61-
FilteringClassLoader classLoader = new FilteringClassLoader(
62-
getClass().getClassLoader());
63-
Class<?> withExampleMetaAnnotation = ClassUtils.forName(
64-
WithExampleMetaAnnotation.class.getName(), classLoader);
65-
Class<Annotation> exampleAnnotationClass = (Class<Annotation>) ClassUtils.forName(
66-
ExampleAnnotation.class.getName(), classLoader);
67-
Class<Annotation> exampleMetaAnnotationClass = (Class<Annotation>) ClassUtils.forName(
68-
ExampleMetaAnnotation.class.getName(), classLoader);
69-
assertThat(AnnotatedElementUtils.getMergedAnnotationAttributes(
70-
withExampleMetaAnnotation, exampleAnnotationClass)).isNull();
71-
assertThat(AnnotatedElementUtils.getMergedAnnotationAttributes(
72-
withExampleMetaAnnotation, exampleMetaAnnotationClass)).isNull();
73-
assertThat(AnnotatedElementUtils.hasAnnotation(withExampleMetaAnnotation,
74-
exampleAnnotationClass)).isFalse();
75-
assertThat(AnnotatedElementUtils.hasAnnotation(withExampleMetaAnnotation,
76-
exampleMetaAnnotationClass)).isFalse();
59+
FilteringClassLoader classLoader = new FilteringClassLoader(getClass().getClassLoader());
60+
Class<?> withAnnotation = ClassUtils.forName(WithExampleMetaAnnotation.class.getName(), classLoader);
61+
Class<Annotation> annotationClass = (Class<Annotation>)
62+
ClassUtils.forName(ExampleAnnotation.class.getName(), classLoader);
63+
Class<Annotation> metaAnnotationClass = (Class<Annotation>)
64+
ClassUtils.forName(ExampleMetaAnnotation.class.getName(), classLoader);
65+
assertThat(AnnotatedElementUtils.getMergedAnnotationAttributes(withAnnotation, annotationClass)).isNull();
66+
assertThat(AnnotatedElementUtils.getMergedAnnotationAttributes(withAnnotation, metaAnnotationClass)).isNull();
67+
assertThat(AnnotatedElementUtils.hasAnnotation(withAnnotation, annotationClass)).isFalse();
68+
assertThat(AnnotatedElementUtils.hasAnnotation(withAnnotation, metaAnnotationClass)).isFalse();
7769
}
7870

7971
@Test
8072
@SuppressWarnings("unchecked")
8173
void filteredTypeInMetaAnnotationWhenUsingMergedAnnotationsHandlesException() throws Exception {
82-
FilteringClassLoader classLoader = new FilteringClassLoader(
83-
getClass().getClassLoader());
84-
Class<?> withExampleMetaAnnotation = ClassUtils.forName(
85-
WithExampleMetaAnnotation.class.getName(), classLoader);
86-
Class<Annotation> exampleAnnotationClass = (Class<Annotation>) ClassUtils.forName(
87-
ExampleAnnotation.class.getName(), classLoader);
88-
Class<Annotation> exampleMetaAnnotationClass = (Class<Annotation>) ClassUtils.forName(
89-
ExampleMetaAnnotation.class.getName(), classLoader);
90-
MergedAnnotations annotations = MergedAnnotations.from(withExampleMetaAnnotation);
91-
assertThat(annotations.get(exampleAnnotationClass).isPresent()).isFalse();
92-
assertThat(annotations.get(exampleMetaAnnotationClass).isPresent()).isFalse();
93-
assertThat(annotations.isPresent(exampleMetaAnnotationClass)).isFalse();
94-
assertThat(annotations.isPresent(exampleAnnotationClass)).isFalse();
74+
FilteringClassLoader classLoader = new FilteringClassLoader(getClass().getClassLoader());
75+
Class<?> withAnnotation = ClassUtils.forName(WithExampleMetaAnnotation.class.getName(), classLoader);
76+
Class<Annotation> annotationClass = (Class<Annotation>)
77+
ClassUtils.forName(ExampleAnnotation.class.getName(), classLoader);
78+
Class<Annotation> metaAnnotationClass = (Class<Annotation>)
79+
ClassUtils.forName(ExampleMetaAnnotation.class.getName(), classLoader);
80+
MergedAnnotations annotations = MergedAnnotations.from(withAnnotation);
81+
assertThat(annotations.get(annotationClass).isPresent()).isFalse();
82+
assertThat(annotations.get(metaAnnotationClass).isPresent()).isFalse();
83+
assertThat(annotations.isPresent(metaAnnotationClass)).isFalse();
84+
assertThat(annotations.isPresent(annotationClass)).isFalse();
9585
}
9686

9787

@@ -103,17 +93,16 @@ static class FilteringClassLoader extends OverridingClassLoader {
10393

10494
@Override
10595
protected boolean isEligibleForOverriding(String className) {
106-
return className.startsWith(
107-
AnnotationIntrospectionFailureTests.class.getName());
96+
return className.startsWith(AnnotationIntrospectionFailureTests.class.getName()) ||
97+
className.startsWith("jdk.internal");
10898
}
10999

110100
@Override
111-
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
112-
if (name.startsWith(AnnotationIntrospectionFailureTests.class.getName()) &&
113-
name.contains("Filtered")) {
101+
protected Class<?> loadClassForOverriding(String name) throws ClassNotFoundException {
102+
if (name.contains("Filtered") || name.startsWith("jdk.internal")) {
114103
throw new ClassNotFoundException(name);
115104
}
116-
return super.loadClass(name, resolve);
105+
return super.loadClassForOverriding(name);
117106
}
118107
}
119108

spring-core/src/test/java/org/springframework/core/annotation/AttributeMethodsTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -112,7 +112,7 @@ void isValidWhenHasTypeNotPresentExceptionReturnsFalse() {
112112
ClassValue annotation = mockAnnotation(ClassValue.class);
113113
given(annotation.value()).willThrow(TypeNotPresentException.class);
114114
AttributeMethods attributes = AttributeMethods.forAnnotationType(annotation.annotationType());
115-
assertThat(attributes.isValid(annotation)).isFalse();
115+
assertThat(attributes.canLoad(annotation)).isFalse();
116116
}
117117

118118
@Test
@@ -121,7 +121,7 @@ void isValidWhenDoesNotHaveTypeNotPresentExceptionReturnsTrue() {
121121
ClassValue annotation = mock();
122122
given(annotation.value()).willReturn((Class) InputStream.class);
123123
AttributeMethods attributes = AttributeMethods.forAnnotationType(annotation.annotationType());
124-
assertThat(attributes.isValid(annotation)).isTrue();
124+
assertThat(attributes.canLoad(annotation)).isTrue();
125125
}
126126

127127
@Test

0 commit comments

Comments
 (0)