Do not inspect meta-annotations on Java annotations

This commit introduces a new isInJavaLangAnnotationPackage(Annotation)
method in AnnotationUtils. This method is now used in AnnotationUtils,
AnnotatedElementUtils, and MetaAnnotationUtils to ensure that search
algorithms do no search for meta-annotations on annotations in the
"java.lang.annotation" package.

The following are some empirical results from this change:

- The number of times that the findAnnotation(Class,Class,Set) method in
  AnnotationUtils is recursively invoked while executing
  AnnotationUtilsTests drops from 51 to 29.

- The number of times that the process(AnnotatedElement) method in
  AnnotationUtils.AnnotationCollector is recursively invoked while
  executing AnnotationUtilsTests.getRepeatableFromMethod() drops
  from 16 to 2.

- The number of times that the doProcess() method in
  AnnotatedElementUtils is recursively invoked while executing the
  "getAnnotationAttributes() On MetaCycleAnnotatedClass with missing
  target meta-annotation" test in AnnotatedElementUtilsTests drops
  from 23 to 5.

- The number of times that the findAnnotationDescriptor(Class,Set,Class)
  method in MetaAnnotationUtils is recursively invoked while executing
  the "findAnnotationDescriptor() on MetaCycleAnnotatedClass with
  missing target meta-annotation" test in MetaAnnotationUtilsTests drops
  from 16 to 8.

Issue: SPR-11483
This commit is contained in:
Sam Brannen
2014-02-25 21:41:59 +01:00
parent 651e0a44fb
commit 979c483384
6 changed files with 92 additions and 10 deletions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2014 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.
@@ -32,6 +32,7 @@ import org.springframework.util.MultiValueMap;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Sam Brannen
* @since 4.0 * @since 4.0
*/ */
public class AnnotatedElementUtils { public class AnnotatedElementUtils {
@@ -159,7 +160,7 @@ public class AnnotatedElementUtils {
for (Annotation annotation : element.getAnnotations()) { for (Annotation annotation : element.getAnnotations()) {
if (annotation.annotationType().getName().equals(annotationType) || depth > 0) { if (annotation.annotationType().getName().equals(annotationType) || depth > 0) {
T result = processor.process(annotation, depth); T result = processor.process(annotation, depth);
if (result != null) { if (result != null) {
return result; return result;
} }
result = doProcess(annotation.annotationType(), annotationType, processor, visited, depth + 1); result = doProcess(annotation.annotationType(), annotationType, processor, visited, depth + 1);
@@ -170,10 +171,12 @@ public class AnnotatedElementUtils {
} }
} }
for (Annotation annotation : element.getAnnotations()) { for (Annotation annotation : element.getAnnotations()) {
T result = doProcess(annotation.annotationType(), annotationType, processor, visited, depth); if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
if (result != null) { T result = doProcess(annotation.annotationType(), annotationType, processor, visited, depth);
processor.postProcess(annotation, result); if (result != null) {
return result; processor.postProcess(annotation, result);
return result;
}
} }
} }
} }

View File

@@ -289,7 +289,7 @@ public abstract class AnnotationUtils {
} }
} }
for (Annotation ann : clazz.getAnnotations()) { for (Annotation ann : clazz.getAnnotations()) {
if (visited.add(ann)) { if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
annotation = findAnnotation(ann.annotationType(), annotationType, visited); annotation = findAnnotation(ann.annotationType(), annotationType, visited);
if (annotation != null) { if (annotation != null) {
return annotation; return annotation;
@@ -422,6 +422,18 @@ public abstract class AnnotationUtils {
return (clazz.isAnnotationPresent(annotationType) && !isAnnotationDeclaredLocally(annotationType, clazz)); return (clazz.isAnnotationPresent(annotationType) && !isAnnotationDeclaredLocally(annotationType, clazz));
} }
/**
* Determine if the supplied {@link Annotation} is defined in the
* {@code java.lang.annotation} package.
*
* @param annotation the annotation to check; never {@code null}
* @return {@code true} if the annotation is in the {@code java.lang.annotation} package
*/
public static boolean isInJavaLangAnnotationPackage(Annotation annotation) {
Assert.notNull(annotation, "Annotation must not be null");
return annotation.annotationType().getName().startsWith("java.lang.annotation");
}
/** /**
* Retrieve the given annotation's attributes as a Map, preserving all attribute types * Retrieve the given annotation's attributes as a Map, preserving all attribute types
* as-is. * as-is.
@@ -631,7 +643,7 @@ public abstract class AnnotationUtils {
else if (ObjectUtils.nullSafeEquals(this.containerAnnotationType, annotation.annotationType())) { else if (ObjectUtils.nullSafeEquals(this.containerAnnotationType, annotation.annotationType())) {
result.addAll(Arrays.asList(getValue(annotation))); result.addAll(Arrays.asList(getValue(annotation)));
} }
else { else if (!isInJavaLangAnnotationPackage(annotation)) {
process(annotation.annotationType()); process(annotation.annotationType());
} }
} }

View File

@@ -16,9 +16,12 @@
package org.springframework.core.annotation; package org.springframework.core.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited; import java.lang.annotation.Inherited;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.Test; import org.junit.Test;
@@ -32,6 +35,13 @@ import static org.junit.Assert.*;
*/ */
public class AnnotatedElementUtilsTests { public class AnnotatedElementUtilsTests {
@Test
public void getAnnotationAttributesOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() {
AnnotationAttributes attributes = AnnotatedElementUtils.getAnnotationAttributes(MetaCycleAnnotatedClass.class,
Transactional.class.getName());
assertNull("Should not find annotation attributes for @Transactional on MetaCycleAnnotatedClass", attributes);
}
@Test @Test
public void getAnnotationAttributesFavorsInheritedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() { public void getAnnotationAttributesFavorsInheritedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() {
AnnotationAttributes attributes = AnnotatedElementUtils.getAnnotationAttributes( AnnotationAttributes attributes = AnnotatedElementUtils.getAnnotationAttributes(
@@ -61,7 +71,34 @@ public class AnnotatedElementUtilsTests {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@MetaCycle3
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@Documented
@interface MetaCycle1 {
}
@MetaCycle1
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@Documented
@interface MetaCycle2 {
}
@MetaCycle2
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@interface MetaCycle3 {
}
@MetaCycle3
static class MetaCycleAnnotatedClass {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Inherited @Inherited
@interface Transactional { @interface Transactional {
@@ -70,12 +107,16 @@ public class AnnotatedElementUtilsTests {
@Transactional @Transactional
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Inherited @Inherited
@interface Composed1 { @interface Composed1 {
} }
@Transactional(readOnly = true) @Transactional(readOnly = true)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@interface Composed2 { @interface Composed2 {
} }

View File

@@ -362,6 +362,13 @@ public class AnnotationUtilsTests {
assertNotNull(order); assertNotNull(order);
} }
@Test
public void findRepeatableAnnotationOnComposedAnnotation() {
Repeatable repeatable = findAnnotation(MyRepeatableMeta.class, Repeatable.class);
assertNotNull(repeatable);
assertEquals(MyRepeatableContainer.class, repeatable.value());
}
@Test @Test
public void getRepeatableFromMethod() throws Exception { public void getRepeatableFromMethod() throws Exception {
Method method = InterfaceWithRepeated.class.getMethod("foo"); Method method = InterfaceWithRepeated.class.getMethod("foo");

View File

@@ -123,7 +123,7 @@ abstract class MetaAnnotationUtils {
// Declared on a composed annotation (i.e., as a meta-annotation)? // Declared on a composed annotation (i.e., as a meta-annotation)?
for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) { for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) {
if (visited.add(composedAnnotation)) { if (!isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) {
AnnotationDescriptor<T> descriptor = findAnnotationDescriptor(composedAnnotation.annotationType(), AnnotationDescriptor<T> descriptor = findAnnotationDescriptor(composedAnnotation.annotationType(),
visited, annotationType); visited, annotationType);
if (descriptor != null) { if (descriptor != null) {
@@ -210,7 +210,7 @@ abstract class MetaAnnotationUtils {
// Declared on a composed annotation (i.e., as a meta-annotation)? // Declared on a composed annotation (i.e., as a meta-annotation)?
for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) { for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) {
if (visited.add(composedAnnotation)) { if (!isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) {
UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(
composedAnnotation.annotationType(), visited, annotationTypes); composedAnnotation.annotationType(), visited, annotationTypes);
if (descriptor != null) { if (descriptor != null) {

View File

@@ -17,8 +17,11 @@
package org.springframework.test.context; package org.springframework.test.context;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.Test; import org.junit.Test;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
@@ -393,42 +396,58 @@ public class MetaAnnotationUtilsTests {
@Component(value = "meta1") @Component(value = "meta1")
@Order @Order
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
static @interface Meta1 { static @interface Meta1 {
} }
@Component(value = "meta2") @Component(value = "meta2")
@Transactional @Transactional
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
static @interface Meta2 { static @interface Meta2 {
} }
@Meta2 @Meta2
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@interface MetaMeta { @interface MetaMeta {
} }
@MetaMeta @MetaMeta
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@interface MetaMetaMeta { @interface MetaMetaMeta {
} }
@MetaCycle3 @MetaCycle3
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@Documented
@interface MetaCycle1 { @interface MetaCycle1 {
} }
@MetaCycle1 @MetaCycle1
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@Documented
@interface MetaCycle2 { @interface MetaCycle2 {
} }
@MetaCycle2 @MetaCycle2
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@interface MetaCycle3 { @interface MetaCycle3 {
} }
@ContextConfiguration @ContextConfiguration
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
static @interface MetaConfig { static @interface MetaConfig {
static class DevConfig { static class DevConfig {