Skip to content

Commit

Permalink
Avoid unnecessary introspection on methods and meta-annotations
Browse files Browse the repository at this point in the history
Issue: SPR-16667
  • Loading branch information
jhoeller committed Mar 30, 2018
1 parent b104897 commit 4da27c2
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -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");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -431,7 +431,8 @@ private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,

@Override
public boolean isMatch(Method candidateMethod) {
return BeanAnnotationHelper.isBeanAnnotated(candidateMethod);
return (candidateMethod.getDeclaringClass() != Object.class &&
BeanAnnotationHelper.isBeanAnnotated(candidateMethod));
}

private ConfigurableBeanFactory getBeanFactory(Object enhancedConfigInstance) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import org.springframework.core.BridgeMethodResolver;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

Expand Down Expand Up @@ -232,7 +233,6 @@ private static boolean hasMetaAnnotationTypes(AnnotatedElement element, @Nullabl

return Boolean.TRUE.equals(
searchWithGetSemantics(element, annotationType, annotationName, new SimpleAnnotationProcessor<Boolean>() {

@Override
@Nullable
public Boolean process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
Expand Down Expand Up @@ -950,7 +950,7 @@ else if (currentAnnotationType == containerType) {
// Recursively search in meta-annotations
for (Annotation annotation : annotations) {
Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) {
if (hasSearchableMetaAnnotations(currentAnnotationType, annotationType, annotationName)) {
T result = searchWithGetSemantics(currentAnnotationType, annotationType,
annotationName, containerType, processor, visited, metaDepth + 1);
if (result != null) {
Expand Down Expand Up @@ -1083,10 +1083,10 @@ else if (currentAnnotationType == containerType) {
}
}

// Search in meta annotations on local annotations
// Recursively search in meta-annotations
for (Annotation annotation : annotations) {
Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) {
if (hasSearchableMetaAnnotations(currentAnnotationType, annotationType, annotationName)) {
T result = searchWithFindSemantics(currentAnnotationType, annotationType, annotationName,
containerType, processor, visited, metaDepth + 1);
if (result != null) {
Expand All @@ -1101,28 +1101,33 @@ else if (currentAnnotationType == containerType) {
}
}

if (aggregatedResults != null) {
if (!CollectionUtils.isEmpty(aggregatedResults)) {
// Prepend to support top-down ordering within class hierarchies
processor.getAggregatedResults().addAll(0, aggregatedResults);
}

if (element instanceof Method) {
Method method = (Method) element;
T result;

// Search on possibly bridged method
Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
T result = searchWithFindSemantics(resolvedMethod, annotationType, annotationName, containerType,
processor, visited, metaDepth);
if (result != null) {
return result;
if (resolvedMethod != method) {
result = searchWithFindSemantics(resolvedMethod, annotationType, annotationName,
containerType, processor, visited, metaDepth);
if (result != null) {
return result;
}
}

// Search on methods in interfaces declared locally
Class<?>[] ifcs = method.getDeclaringClass().getInterfaces();
result = searchOnInterfaces(method, annotationType, annotationName, containerType, processor,
visited, metaDepth, ifcs);
if (result != null) {
return result;
if (ifcs.length > 0) {
result = searchOnInterfaces(method, annotationType, annotationName, containerType,
processor, visited, metaDepth, ifcs);
if (result != null) {
return result;
}
}

// Search on methods in class hierarchy and interface hierarchy
Expand Down Expand Up @@ -1189,10 +1194,10 @@ private static <T> T searchOnInterfaces(Method method, @Nullable Class<? extends
@Nullable String annotationName, @Nullable Class<? extends Annotation> containerType,
Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth, Class<?>[] ifcs) {

for (Class<?> iface : ifcs) {
if (AnnotationUtils.isInterfaceWithAnnotatedMethods(iface)) {
for (Class<?> ifc : ifcs) {
if (AnnotationUtils.isInterfaceWithAnnotatedMethods(ifc)) {
try {
Method equivalentMethod = iface.getMethod(method.getName(), method.getParameterTypes());
Method equivalentMethod = ifc.getMethod(method.getName(), method.getParameterTypes());
T result = searchWithFindSemantics(equivalentMethod, annotationType, annotationName, containerType,
processor, visited, metaDepth);
if (result != null) {
Expand All @@ -1208,6 +1213,26 @@ private static <T> T searchOnInterfaces(Method method, @Nullable Class<? extends
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}
* attribute of the supplied repeatable annotation {@code container}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.springframework.core.BridgeMethodResolver;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
Expand Down Expand Up @@ -147,18 +148,18 @@ public abstract class AnnotationUtils {
* <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
* {@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
* @return the first matching annotation, or {@code null} if not found
* @since 4.0
*/
@SuppressWarnings("unchecked")
@Nullable
public static <A extends Annotation> A getAnnotation(Annotation ann, Class<A> annotationType) {
if (annotationType.isInstance(ann)) {
return synthesizeAnnotation((A) ann);
public static <A extends Annotation> A getAnnotation(Annotation annotation, Class<A> annotationType) {
if (annotationType.isInstance(annotation)) {
return synthesizeAnnotation((A) annotation);
}
Class<? extends Annotation> annotatedElement = ann.annotationType();
Class<? extends Annotation> annotatedElement = annotation.annotationType();
try {
return synthesizeAnnotation(annotatedElement.getAnnotation(annotationType), annotatedElement);
}
Expand Down Expand Up @@ -574,10 +575,10 @@ public static <A extends Annotation> A findAnnotation(Method method, @Nullable C
@Nullable
private static <A extends Annotation> A searchOnInterfaces(Method method, Class<A> annotationType, Class<?>... ifcs) {
A annotation = null;
for (Class<?> iface : ifcs) {
if (isInterfaceWithAnnotatedMethods(iface)) {
for (Class<?> ifc : ifcs) {
if (isInterfaceWithAnnotatedMethods(ifc)) {
try {
Method equivalentMethod = iface.getMethod(method.getName(), method.getParameterTypes());
Method equivalentMethod = ifc.getMethod(method.getName(), method.getParameterTypes());
annotation = getAnnotation(equivalentMethod, annotationType);
}
catch (NoSuchMethodException ex) {
Expand All @@ -591,15 +592,20 @@ private static <A extends Annotation> A searchOnInterfaces(Method method, Class<
return annotation;
}

static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
Boolean found = annotatedInterfaceCache.get(iface);
static boolean isInterfaceWithAnnotatedMethods(Class<?> ifc) {
if (ClassUtils.isJavaLanguageInterface(ifc)) {
return false;
}

Boolean found = annotatedInterfaceCache.get(ifc);
if (found != null) {
return found;
}
found = Boolean.FALSE;
for (Method ifcMethod : iface.getMethods()) {
for (Method ifcMethod : ifc.getMethods()) {
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;
break;
}
Expand All @@ -608,7 +614,7 @@ static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
handleIntrospectionFailure(ifcMethod, ex);
}
}
annotatedInterfaceCache.put(iface, found);
annotatedInterfaceCache.put(ifc, found);
return found;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ protected Boolean hasAnnotation(String typeName) {
}
else if (typeName.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;
}
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -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");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -36,6 +36,7 @@

import org.springframework.core.Ordered;
import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
Expand Down Expand Up @@ -1541,6 +1542,13 @@ public void synthesizeAnnotationWithArrayOfChars() throws Exception {
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
static <T> T[] asArray(T... arr) {
Expand Down Expand Up @@ -1634,6 +1642,12 @@ public interface AnnotatedInterface {
void fromInterfaceImplementedByRoot();
}

public interface NullableAnnotatedInterface {

@Nullable
void fromInterfaceImplementedByRoot();
}

public static class Root implements AnnotatedInterface {

@Order(27)
Expand Down

0 comments on commit 4da27c2

Please sign in to comment.