Skip to content

Commit 129c05b

Browse files
committed
Comprehensively cache annotated methods for interfaces and superclasses
Issue: SPR-16675
1 parent b8d3209 commit 129c05b

File tree

4 files changed

+158
-145
lines changed

4 files changed

+158
-145
lines changed

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

Lines changed: 74 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,17 +1050,48 @@ private static <T> T searchWithFindSemantics(AnnotatedElement element,
10501050
try {
10511051
// Locally declared annotations (ignoring @Inherited)
10521052
Annotation[] annotations = element.getDeclaredAnnotations();
1053-
List<T> aggregatedResults = (processor.aggregates() ? new ArrayList<>() : null);
1054-
1055-
// Search in local annotations
1056-
for (Annotation annotation : annotations) {
1057-
Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
1058-
if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) {
1059-
if (currentAnnotationType == annotationType ||
1060-
currentAnnotationType.getName().equals(annotationName) ||
1061-
processor.alwaysProcesses()) {
1062-
T result = processor.process(element, annotation, metaDepth);
1053+
if (annotations.length > 0) {
1054+
List<T> aggregatedResults = (processor.aggregates() ? new ArrayList<>() : null);
1055+
1056+
// Search in local annotations
1057+
for (Annotation annotation : annotations) {
1058+
Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
1059+
if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) {
1060+
if (currentAnnotationType == annotationType ||
1061+
currentAnnotationType.getName().equals(annotationName) ||
1062+
processor.alwaysProcesses()) {
1063+
T result = processor.process(element, annotation, metaDepth);
1064+
if (result != null) {
1065+
if (aggregatedResults != null && metaDepth == 0) {
1066+
aggregatedResults.add(result);
1067+
}
1068+
else {
1069+
return result;
1070+
}
1071+
}
1072+
}
1073+
// Repeatable annotations in container?
1074+
else if (currentAnnotationType == containerType) {
1075+
for (Annotation contained : getRawAnnotationsFromContainer(element, annotation)) {
1076+
T result = processor.process(element, contained, metaDepth);
1077+
if (aggregatedResults != null && result != null) {
1078+
// No need to post-process since repeatable annotations within a
1079+
// container cannot be composed annotations.
1080+
aggregatedResults.add(result);
1081+
}
1082+
}
1083+
}
1084+
}
1085+
}
1086+
1087+
// Recursively search in meta-annotations
1088+
for (Annotation annotation : annotations) {
1089+
Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
1090+
if (hasSearchableMetaAnnotations(currentAnnotationType, annotationType, annotationName)) {
1091+
T result = searchWithFindSemantics(currentAnnotationType, annotationType, annotationName,
1092+
containerType, processor, visited, metaDepth + 1);
10631093
if (result != null) {
1094+
processor.postProcess(currentAnnotationType, annotation, result);
10641095
if (aggregatedResults != null && metaDepth == 0) {
10651096
aggregatedResults.add(result);
10661097
}
@@ -1069,43 +1100,14 @@ private static <T> T searchWithFindSemantics(AnnotatedElement element,
10691100
}
10701101
}
10711102
}
1072-
// Repeatable annotations in container?
1073-
else if (currentAnnotationType == containerType) {
1074-
for (Annotation contained : getRawAnnotationsFromContainer(element, annotation)) {
1075-
T result = processor.process(element, contained, metaDepth);
1076-
if (aggregatedResults != null && result != null) {
1077-
// No need to post-process since repeatable annotations within a
1078-
// container cannot be composed annotations.
1079-
aggregatedResults.add(result);
1080-
}
1081-
}
1082-
}
10831103
}
1084-
}
10851104

1086-
// Recursively search in meta-annotations
1087-
for (Annotation annotation : annotations) {
1088-
Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
1089-
if (hasSearchableMetaAnnotations(currentAnnotationType, annotationType, annotationName)) {
1090-
T result = searchWithFindSemantics(currentAnnotationType, annotationType, annotationName,
1091-
containerType, processor, visited, metaDepth + 1);
1092-
if (result != null) {
1093-
processor.postProcess(currentAnnotationType, annotation, result);
1094-
if (aggregatedResults != null && metaDepth == 0) {
1095-
aggregatedResults.add(result);
1096-
}
1097-
else {
1098-
return result;
1099-
}
1100-
}
1105+
if (!CollectionUtils.isEmpty(aggregatedResults)) {
1106+
// Prepend to support top-down ordering within class hierarchies
1107+
processor.getAggregatedResults().addAll(0, aggregatedResults);
11011108
}
11021109
}
11031110

1104-
if (!CollectionUtils.isEmpty(aggregatedResults)) {
1105-
// Prepend to support top-down ordering within class hierarchies
1106-
processor.getAggregatedResults().addAll(0, aggregatedResults);
1107-
}
1108-
11091111
if (element instanceof Method) {
11101112
Method method = (Method) element;
11111113
T result;
@@ -1123,8 +1125,8 @@ else if (currentAnnotationType == containerType) {
11231125
// Search on methods in interfaces declared locally
11241126
Class<?>[] ifcs = method.getDeclaringClass().getInterfaces();
11251127
if (ifcs.length > 0) {
1126-
result = searchOnInterfaces(method, annotationType, annotationName, containerType,
1127-
processor, visited, metaDepth, ifcs);
1128+
result = searchOnInterfaces(method, annotationType, annotationName,
1129+
containerType, processor, visited, metaDepth, ifcs);
11281130
if (result != null) {
11291131
return result;
11301132
}
@@ -1137,23 +1139,23 @@ else if (currentAnnotationType == containerType) {
11371139
if (clazz == null || Object.class == clazz) {
11381140
break;
11391141
}
1140-
1141-
try {
1142-
Method equivalentMethod = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
1143-
Method resolvedEquivalentMethod = BridgeMethodResolver.findBridgedMethod(equivalentMethod);
1144-
result = searchWithFindSemantics(resolvedEquivalentMethod, annotationType, annotationName,
1145-
containerType, processor, visited, metaDepth);
1146-
if (result != null) {
1147-
return result;
1142+
Set<Method> annotatedMethods = AnnotationUtils.getAnnotatedMethodsInBaseType(clazz);
1143+
if (!annotatedMethods.isEmpty()) {
1144+
for (Method annotatedMethod : annotatedMethods) {
1145+
if (annotatedMethod.getName().equals(method.getName()) &&
1146+
Arrays.equals(annotatedMethod.getParameterTypes(), method.getParameterTypes())) {
1147+
Method resolvedSuperMethod = BridgeMethodResolver.findBridgedMethod(annotatedMethod);
1148+
result = searchWithFindSemantics(resolvedSuperMethod, annotationType, annotationName,
1149+
containerType, processor, visited, metaDepth);
1150+
if (result != null) {
1151+
return result;
1152+
}
1153+
}
11481154
}
11491155
}
1150-
catch (NoSuchMethodException ex) {
1151-
// No equivalent method found
1152-
}
1153-
11541156
// Search on interfaces declared on superclass
1155-
result = searchOnInterfaces(method, annotationType, annotationName, containerType, processor,
1156-
visited, metaDepth, clazz.getInterfaces());
1157+
result = searchOnInterfaces(method, annotationType, annotationName,
1158+
containerType, processor, visited, metaDepth, clazz.getInterfaces());
11571159
if (result != null) {
11581160
return result;
11591161
}
@@ -1164,8 +1166,8 @@ else if (element instanceof Class) {
11641166

11651167
// Search on interfaces
11661168
for (Class<?> ifc : clazz.getInterfaces()) {
1167-
T result = searchWithFindSemantics(ifc, annotationType, annotationName, containerType,
1168-
processor, visited, metaDepth);
1169+
T result = searchWithFindSemantics(ifc, annotationType, annotationName,
1170+
containerType, processor, visited, metaDepth);
11691171
if (result != null) {
11701172
return result;
11711173
}
@@ -1174,8 +1176,8 @@ else if (element instanceof Class) {
11741176
// Search on superclass
11751177
Class<?> superclass = clazz.getSuperclass();
11761178
if (superclass != null && Object.class != superclass) {
1177-
T result = searchWithFindSemantics(superclass, annotationType, annotationName, containerType,
1178-
processor, visited, metaDepth);
1179+
T result = searchWithFindSemantics(superclass, annotationType, annotationName,
1180+
containerType, processor, visited, metaDepth);
11791181
if (result != null) {
11801182
return result;
11811183
}
@@ -1195,18 +1197,18 @@ private static <T> T searchOnInterfaces(Method method, @Nullable Class<? extends
11951197
Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth, Class<?>[] ifcs) {
11961198

11971199
for (Class<?> ifc : ifcs) {
1198-
if (AnnotationUtils.isInterfaceWithAnnotatedMethods(ifc)) {
1199-
try {
1200-
Method equivalentMethod = ifc.getMethod(method.getName(), method.getParameterTypes());
1201-
T result = searchWithFindSemantics(equivalentMethod, annotationType, annotationName, containerType,
1202-
processor, visited, metaDepth);
1203-
if (result != null) {
1204-
return result;
1200+
Set<Method> annotatedMethods = AnnotationUtils.getAnnotatedMethodsInBaseType(ifc);
1201+
if (!annotatedMethods.isEmpty()) {
1202+
for (Method annotatedMethod : annotatedMethods) {
1203+
if (annotatedMethod.getName().equals(method.getName()) &&
1204+
Arrays.equals(annotatedMethod.getParameterTypes(), method.getParameterTypes())) {
1205+
T result = searchWithFindSemantics(annotatedMethod, annotationType, annotationName,
1206+
containerType, processor, visited, metaDepth);
1207+
if (result != null) {
1208+
return result;
1209+
}
12051210
}
12061211
}
1207-
catch (NoSuchMethodException ex) {
1208-
// Skip this interface - it doesn't have the method...
1209-
}
12101212
}
12111213
}
12121214

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

Lines changed: 58 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.lang.reflect.Modifier;
2727
import java.lang.reflect.Proxy;
2828
import java.util.ArrayList;
29+
import java.util.Arrays;
2930
import java.util.Collections;
3031
import java.util.HashSet;
3132
import java.util.LinkedHashMap;
@@ -122,7 +123,7 @@ public abstract class AnnotationUtils {
122123
private static final Map<AnnotationCacheKey, Boolean> metaPresentCache =
123124
new ConcurrentReferenceHashMap<>(256);
124125

125-
private static final Map<Class<?>, Boolean> annotatedInterfaceCache =
126+
private static final Map<Class<?>, Set<Method>> annotatedBaseTypeCache =
126127
new ConcurrentReferenceHashMap<>(256);
127128

128129
private static final Map<Class<? extends Annotation>, Boolean> synthesizableCache =
@@ -539,7 +540,6 @@ public static <A extends Annotation> A findAnnotation(Method method, @Nullable C
539540
if (result == null) {
540541
Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
541542
result = findAnnotation((AnnotatedElement) resolvedMethod, annotationType);
542-
543543
if (result == null) {
544544
result = searchOnInterfaces(method, annotationType, method.getDeclaringClass().getInterfaces());
545545
}
@@ -574,48 +574,63 @@ public static <A extends Annotation> A findAnnotation(Method method, @Nullable C
574574

575575
@Nullable
576576
private static <A extends Annotation> A searchOnInterfaces(Method method, Class<A> annotationType, Class<?>... ifcs) {
577-
A annotation = null;
578577
for (Class<?> ifc : ifcs) {
579-
if (isInterfaceWithAnnotatedMethods(ifc)) {
580-
try {
581-
Method equivalentMethod = ifc.getMethod(method.getName(), method.getParameterTypes());
582-
annotation = getAnnotation(equivalentMethod, annotationType);
583-
}
584-
catch (NoSuchMethodException ex) {
585-
// Skip this interface - it doesn't have the method...
586-
}
587-
if (annotation != null) {
588-
break;
578+
Set<Method> annotatedMethods = getAnnotatedMethodsInBaseType(ifc);
579+
if (!annotatedMethods.isEmpty()) {
580+
for (Method annotatedMethod : annotatedMethods) {
581+
if (annotatedMethod.getName().equals(method.getName()) &&
582+
Arrays.equals(annotatedMethod.getParameterTypes(), method.getParameterTypes())) {
583+
A annotation = getAnnotation(annotatedMethod, annotationType);
584+
if (annotation != null) {
585+
return annotation;
586+
}
587+
}
589588
}
590589
}
591590
}
592-
return annotation;
591+
return null;
593592
}
594593

595-
static boolean isInterfaceWithAnnotatedMethods(Class<?> ifc) {
596-
if (ClassUtils.isJavaLanguageInterface(ifc)) {
597-
return false;
594+
static Set<Method> getAnnotatedMethodsInBaseType(Class<?> baseType) {
595+
if (ClassUtils.isJavaLanguageInterface(baseType)) {
596+
return Collections.emptySet();
598597
}
599598

600-
Boolean found = annotatedInterfaceCache.get(ifc);
601-
if (found != null) {
602-
return found;
599+
Set<Method> annotatedMethods = annotatedBaseTypeCache.get(baseType);
600+
if (annotatedMethods != null) {
601+
return annotatedMethods;
603602
}
604-
found = Boolean.FALSE;
605-
for (Method ifcMethod : ifc.getMethods()) {
603+
Method[] methods = (baseType.isInterface() ? baseType.getMethods() : baseType.getDeclaredMethods());
604+
for (Method baseMethod : methods) {
606605
try {
607-
Annotation[] anns = ifcMethod.getAnnotations();
608-
if (anns.length > 1 || (anns.length == 1 && anns[0].annotationType() != Nullable.class)) {
609-
found = Boolean.TRUE;
610-
break;
606+
if (hasSearchableAnnotations(baseMethod)) {
607+
if (annotatedMethods == null) {
608+
annotatedMethods = new HashSet<>();
609+
}
610+
annotatedMethods.add(baseMethod);
611611
}
612612
}
613613
catch (Throwable ex) {
614-
handleIntrospectionFailure(ifcMethod, ex);
614+
handleIntrospectionFailure(baseMethod, ex);
615615
}
616616
}
617-
annotatedInterfaceCache.put(ifc, found);
618-
return found;
617+
if (annotatedMethods == null) {
618+
annotatedMethods = Collections.emptySet();
619+
}
620+
annotatedBaseTypeCache.put(baseType, annotatedMethods);
621+
return annotatedMethods;
622+
}
623+
624+
private static boolean hasSearchableAnnotations(Method ifcMethod) {
625+
Annotation[] anns = ifcMethod.getAnnotations();
626+
if (anns.length == 0) {
627+
return false;
628+
}
629+
if (anns.length == 1) {
630+
Class<?> annType = anns[0].annotationType();
631+
return (annType != Nullable.class && annType != Deprecated.class);
632+
}
633+
return true;
619634
}
620635

621636
/**
@@ -1875,6 +1890,20 @@ static void handleIntrospectionFailure(@Nullable AnnotatedElement element, Throw
18751890
}
18761891
}
18771892

1893+
/**
1894+
* Clear the internal annotation metadata cache.
1895+
* @since 4.3.15
1896+
*/
1897+
public static void clearCache() {
1898+
findAnnotationCache.clear();
1899+
metaPresentCache.clear();
1900+
annotatedBaseTypeCache.clear();
1901+
synthesizableCache.clear();
1902+
attributeAliasesCache.clear();
1903+
attributeMethodsCache.clear();
1904+
aliasDescriptorCache.clear();
1905+
}
1906+
18781907

18791908
/**
18801909
* Cache key for the AnnotatedElement cache.

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

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import java.lang.annotation.Retention;
2323
import java.lang.annotation.RetentionPolicy;
2424
import java.lang.annotation.Target;
25-
import java.lang.reflect.Field;
2625
import java.lang.reflect.Method;
2726
import java.util.Collections;
2827
import java.util.List;
@@ -39,7 +38,6 @@
3938
import org.springframework.lang.Nullable;
4039
import org.springframework.stereotype.Component;
4140
import org.springframework.util.ClassUtils;
42-
import org.springframework.util.ReflectionUtils;
4341

4442
import static java.util.Arrays.*;
4543
import static java.util.stream.Collectors.*;
@@ -64,23 +62,8 @@ public class AnnotationUtilsTests {
6462

6563

6664
@Before
67-
public void clearCachesBeforeTests() {
68-
clearCaches();
69-
}
70-
71-
static void clearCaches() {
72-
clearCache("findAnnotationCache", "annotatedInterfaceCache", "metaPresentCache", "synthesizableCache",
73-
"attributeAliasesCache", "attributeMethodsCache", "aliasDescriptorCache");
74-
}
75-
76-
static void clearCache(String... cacheNames) {
77-
stream(cacheNames).forEach(cacheName -> getCache(cacheName).clear());
78-
}
79-
80-
static Map<?, ?> getCache(String cacheName) {
81-
Field field = ReflectionUtils.findField(AnnotationUtils.class, cacheName);
82-
ReflectionUtils.makeAccessible(field);
83-
return (Map<?, ?>) ReflectionUtils.getField(field, null);
65+
public void clearCacheBeforeTests() {
66+
AnnotationUtils.clearCache();
8467
}
8568

8669

@@ -1544,9 +1527,9 @@ public void synthesizeAnnotationWithArrayOfChars() throws Exception {
15441527

15451528
@Test
15461529
public void interfaceWithAnnotatedMethods() {
1547-
assertFalse(AnnotationUtils.isInterfaceWithAnnotatedMethods(NonAnnotatedInterface.class));
1548-
assertTrue(AnnotationUtils.isInterfaceWithAnnotatedMethods(AnnotatedInterface.class));
1549-
assertFalse(AnnotationUtils.isInterfaceWithAnnotatedMethods(NullableAnnotatedInterface.class));
1530+
assertTrue(AnnotationUtils.getAnnotatedMethodsInBaseType(NonAnnotatedInterface.class).isEmpty());
1531+
assertFalse(AnnotationUtils.getAnnotatedMethodsInBaseType(AnnotatedInterface.class).isEmpty());
1532+
assertTrue(AnnotationUtils.getAnnotatedMethodsInBaseType(NullableAnnotatedInterface.class).isEmpty());
15501533
}
15511534

15521535

0 commit comments

Comments
 (0)