Avoid unnecessary introspection on methods and meta-annotations

Issue: SPR-16667
This commit is contained in:
Juergen Hoeller
2018-03-31 00:18:14 +02:00
parent b1048975d2
commit 4da27c2a73
5 changed files with 80 additions and 33 deletions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2017 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -431,7 +431,8 @@ class ConfigurationClassEnhancer {
@Override @Override
public boolean isMatch(Method candidateMethod) { public boolean isMatch(Method candidateMethod) {
return BeanAnnotationHelper.isBeanAnnotated(candidateMethod); return (candidateMethod.getDeclaringClass() != Object.class &&
BeanAnnotationHelper.isBeanAnnotated(candidateMethod));
} }
private ConfigurableBeanFactory getBeanFactory(Object enhancedConfigInstance) { private ConfigurableBeanFactory getBeanFactory(Object enhancedConfigInstance) {

View File

@@ -30,6 +30,7 @@ import java.util.Set;
import org.springframework.core.BridgeMethodResolver; import org.springframework.core.BridgeMethodResolver;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
@@ -232,7 +233,6 @@ public class AnnotatedElementUtils {
return Boolean.TRUE.equals( return Boolean.TRUE.equals(
searchWithGetSemantics(element, annotationType, annotationName, new SimpleAnnotationProcessor<Boolean>() { searchWithGetSemantics(element, annotationType, annotationName, new SimpleAnnotationProcessor<Boolean>() {
@Override @Override
@Nullable @Nullable
public Boolean process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) { public Boolean process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
@@ -950,7 +950,7 @@ public class AnnotatedElementUtils {
// Recursively search in meta-annotations // Recursively search in meta-annotations
for (Annotation annotation : annotations) { for (Annotation annotation : annotations) {
Class<? extends Annotation> currentAnnotationType = annotation.annotationType(); Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) { if (hasSearchableMetaAnnotations(currentAnnotationType, annotationType, annotationName)) {
T result = searchWithGetSemantics(currentAnnotationType, annotationType, T result = searchWithGetSemantics(currentAnnotationType, annotationType,
annotationName, containerType, processor, visited, metaDepth + 1); annotationName, containerType, processor, visited, metaDepth + 1);
if (result != null) { if (result != null) {
@@ -1083,10 +1083,10 @@ public class AnnotatedElementUtils {
} }
} }
// Search in meta annotations on local annotations // Recursively search in meta-annotations
for (Annotation annotation : annotations) { for (Annotation annotation : annotations) {
Class<? extends Annotation> currentAnnotationType = annotation.annotationType(); Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) { if (hasSearchableMetaAnnotations(currentAnnotationType, annotationType, annotationName)) {
T result = searchWithFindSemantics(currentAnnotationType, annotationType, annotationName, T result = searchWithFindSemantics(currentAnnotationType, annotationType, annotationName,
containerType, processor, visited, metaDepth + 1); containerType, processor, visited, metaDepth + 1);
if (result != null) { if (result != null) {
@@ -1101,28 +1101,33 @@ public class AnnotatedElementUtils {
} }
} }
if (aggregatedResults != null) { if (!CollectionUtils.isEmpty(aggregatedResults)) {
// Prepend to support top-down ordering within class hierarchies // Prepend to support top-down ordering within class hierarchies
processor.getAggregatedResults().addAll(0, aggregatedResults); processor.getAggregatedResults().addAll(0, aggregatedResults);
} }
if (element instanceof Method) { if (element instanceof Method) {
Method method = (Method) element; Method method = (Method) element;
T result;
// Search on possibly bridged method // Search on possibly bridged method
Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method); Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
T result = searchWithFindSemantics(resolvedMethod, annotationType, annotationName, containerType, if (resolvedMethod != method) {
processor, visited, metaDepth); result = searchWithFindSemantics(resolvedMethod, annotationType, annotationName,
if (result != null) { containerType, processor, visited, metaDepth);
return result; if (result != null) {
return result;
}
} }
// Search on methods in interfaces declared locally // Search on methods in interfaces declared locally
Class<?>[] ifcs = method.getDeclaringClass().getInterfaces(); Class<?>[] ifcs = method.getDeclaringClass().getInterfaces();
result = searchOnInterfaces(method, annotationType, annotationName, containerType, processor, if (ifcs.length > 0) {
visited, metaDepth, ifcs); result = searchOnInterfaces(method, annotationType, annotationName, containerType,
if (result != null) { processor, visited, metaDepth, ifcs);
return result; if (result != null) {
return result;
}
} }
// Search on methods in class hierarchy and interface hierarchy // Search on methods in class hierarchy and interface hierarchy
@@ -1189,10 +1194,10 @@ public class AnnotatedElementUtils {
@Nullable String annotationName, @Nullable Class<? extends Annotation> containerType, @Nullable String annotationName, @Nullable Class<? extends Annotation> containerType,
Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth, Class<?>[] ifcs) { Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth, Class<?>[] ifcs) {
for (Class<?> iface : ifcs) { for (Class<?> ifc : ifcs) {
if (AnnotationUtils.isInterfaceWithAnnotatedMethods(iface)) { if (AnnotationUtils.isInterfaceWithAnnotatedMethods(ifc)) {
try { try {
Method equivalentMethod = iface.getMethod(method.getName(), method.getParameterTypes()); Method equivalentMethod = ifc.getMethod(method.getName(), method.getParameterTypes());
T result = searchWithFindSemantics(equivalentMethod, annotationType, annotationName, containerType, T result = searchWithFindSemantics(equivalentMethod, annotationType, annotationName, containerType,
processor, visited, metaDepth); processor, visited, metaDepth);
if (result != null) { if (result != null) {
@@ -1208,6 +1213,26 @@ public class AnnotatedElementUtils {
return null; return null;
} }
/**
* Determine whether the current annotation type is generally expected to have
* meta-annotations of the specified annotation type that we're searching for,
* explicitly excluding some common cases that would never deliver any results.
*/
private static boolean hasSearchableMetaAnnotations(Class<? extends Annotation> currentAnnotationType,
@Nullable Class<?> annotationType, @Nullable String annotationName) {
if (AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) {
return false;
}
if (currentAnnotationType == Nullable.class || currentAnnotationType.getName().startsWith("java")) {
// @Nullable and standard Java annotations are only meant to have standard Java meta-annotations
// -> not worth searching otherwise.
return ((annotationType != null && annotationType.getName().startsWith("java")) ||
(annotationName != null && annotationName.startsWith("java")));
}
return true;
}
/** /**
* Get the array of raw (unsynthesized) annotations from the {@code value} * Get the array of raw (unsynthesized) annotations from the {@code value}
* attribute of the supplied repeatable annotation {@code container}. * attribute of the supplied repeatable annotation {@code container}.

View File

@@ -40,6 +40,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.core.BridgeMethodResolver; import org.springframework.core.BridgeMethodResolver;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
@@ -147,18 +148,18 @@ public abstract class AnnotationUtils {
* <p>Note that this method supports only a single level of meta-annotations. * <p>Note that this method supports only a single level of meta-annotations.
* For support for arbitrary levels of meta-annotations, use one of the * For support for arbitrary levels of meta-annotations, use one of the
* {@code find*()} methods instead. * {@code find*()} methods instead.
* @param ann the Annotation to check * @param annotation the Annotation to check
* @param annotationType the annotation type to look for, both locally and as a meta-annotation * @param annotationType the annotation type to look for, both locally and as a meta-annotation
* @return the first matching annotation, or {@code null} if not found * @return the first matching annotation, or {@code null} if not found
* @since 4.0 * @since 4.0
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Nullable @Nullable
public static <A extends Annotation> A getAnnotation(Annotation ann, Class<A> annotationType) { public static <A extends Annotation> A getAnnotation(Annotation annotation, Class<A> annotationType) {
if (annotationType.isInstance(ann)) { if (annotationType.isInstance(annotation)) {
return synthesizeAnnotation((A) ann); return synthesizeAnnotation((A) annotation);
} }
Class<? extends Annotation> annotatedElement = ann.annotationType(); Class<? extends Annotation> annotatedElement = annotation.annotationType();
try { try {
return synthesizeAnnotation(annotatedElement.getAnnotation(annotationType), annotatedElement); return synthesizeAnnotation(annotatedElement.getAnnotation(annotationType), annotatedElement);
} }
@@ -574,10 +575,10 @@ public abstract class AnnotationUtils {
@Nullable @Nullable
private static <A extends Annotation> A searchOnInterfaces(Method method, Class<A> annotationType, Class<?>... ifcs) { private static <A extends Annotation> A searchOnInterfaces(Method method, Class<A> annotationType, Class<?>... ifcs) {
A annotation = null; A annotation = null;
for (Class<?> iface : ifcs) { for (Class<?> ifc : ifcs) {
if (isInterfaceWithAnnotatedMethods(iface)) { if (isInterfaceWithAnnotatedMethods(ifc)) {
try { try {
Method equivalentMethod = iface.getMethod(method.getName(), method.getParameterTypes()); Method equivalentMethod = ifc.getMethod(method.getName(), method.getParameterTypes());
annotation = getAnnotation(equivalentMethod, annotationType); annotation = getAnnotation(equivalentMethod, annotationType);
} }
catch (NoSuchMethodException ex) { catch (NoSuchMethodException ex) {
@@ -591,15 +592,20 @@ public abstract class AnnotationUtils {
return annotation; return annotation;
} }
static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) { static boolean isInterfaceWithAnnotatedMethods(Class<?> ifc) {
Boolean found = annotatedInterfaceCache.get(iface); if (ClassUtils.isJavaLanguageInterface(ifc)) {
return false;
}
Boolean found = annotatedInterfaceCache.get(ifc);
if (found != null) { if (found != null) {
return found; return found;
} }
found = Boolean.FALSE; found = Boolean.FALSE;
for (Method ifcMethod : iface.getMethods()) { for (Method ifcMethod : ifc.getMethods()) {
try { try {
if (ifcMethod.getAnnotations().length > 0) { Annotation[] anns = ifcMethod.getAnnotations();
if (anns.length > 1 || (anns.length == 1 && anns[0].annotationType() != Nullable.class)) {
found = Boolean.TRUE; found = Boolean.TRUE;
break; break;
} }
@@ -608,7 +614,7 @@ public abstract class AnnotationUtils {
handleIntrospectionFailure(ifcMethod, ex); handleIntrospectionFailure(ifcMethod, ex);
} }
} }
annotatedInterfaceCache.put(iface, found); annotatedInterfaceCache.put(ifc, found);
return found; return found;
} }

View File

@@ -114,7 +114,8 @@ public class AnnotationTypeFilter extends AbstractTypeHierarchyTraversingFilter
} }
else if (typeName.startsWith("java")) { else if (typeName.startsWith("java")) {
if (!this.annotationType.getName().startsWith("java")) { if (!this.annotationType.getName().startsWith("java")) {
// Standard Java classes don't have non-standard annotations on them. // Standard Java types do not have non-standard annotations on them ->
// skip any load attempt, in particular for Java language interfaces.
return false; return false;
} }
try { try {

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2017 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass; import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
@@ -1541,6 +1542,13 @@ public class AnnotationUtilsTests {
assertArrayEquals(new char[] { 'x', 'y', 'z' }, chars); assertArrayEquals(new char[] { 'x', 'y', 'z' }, chars);
} }
@Test
public void interfaceWithAnnotatedMethods() {
assertFalse(AnnotationUtils.isInterfaceWithAnnotatedMethods(NonAnnotatedInterface.class));
assertTrue(AnnotationUtils.isInterfaceWithAnnotatedMethods(AnnotatedInterface.class));
assertFalse(AnnotationUtils.isInterfaceWithAnnotatedMethods(NullableAnnotatedInterface.class));
}
@SafeVarargs @SafeVarargs
static <T> T[] asArray(T... arr) { static <T> T[] asArray(T... arr) {
@@ -1634,6 +1642,12 @@ public class AnnotationUtilsTests {
void fromInterfaceImplementedByRoot(); void fromInterfaceImplementedByRoot();
} }
public interface NullableAnnotatedInterface {
@Nullable
void fromInterfaceImplementedByRoot();
}
public static class Root implements AnnotatedInterface { public static class Root implements AnnotatedInterface {
@Order(27) @Order(27)