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:
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user