From ebed52cc223c2e66d7103e34ab4a4b37b866859c Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 13 May 2015 15:41:19 +0200 Subject: [PATCH] Favor local, composed annotations in AnnotatedElementUtils This commit updates the "get semantics" search algorithm used in `AnnotatedElementUtils` so that locally declared 'composed annotations' are favored over inherited annotations. Specifically, the internal `searchWithGetSemantics()` method now searches locally declared annotations before searching inherited annotations. All TODOs in `AnnotatedElementUtilsTests` have been completed, and all ignored tests have been reinstated. Issue: SPR-11598 --- .../annotation/AnnotatedElementUtils.java | 155 ++++++++++-------- .../AnnotatedElementUtilsTests.java | 115 +++++++------ .../core/annotation/AnnotationUtilsTests.java | 15 +- 3 files changed, 162 insertions(+), 123 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java index 6acc562221..e559ffb2db 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java @@ -19,8 +19,11 @@ package org.springframework.core.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -71,11 +74,13 @@ import org.springframework.util.MultiValueMap; * *

Support for {@code @Inherited}

*

Methods following get semantics will honor the contract of - * Java's {@link java.lang.annotation.Inherited @Inherited} annotation. - * However, methods following find semantics will ignore the - * presence of {@code @Inherited} since the find search algorithm - * manually traverses type and method hierarchies and thereby implicitly - * supports annotation inheritance without the need for {@code @Inherited}. + * Java's {@link java.lang.annotation.Inherited @Inherited} annotation except + * that locally declared annotations (including custom composed annotations) + * will be favored over inherited annotations. In contrast, methods following + * find semantics will completely ignore the presence of + * {@code @Inherited} since the find search algorithm manually + * traverses type and method hierarchies and thereby implicitly supports + * annotation inheritance without the need for {@code @Inherited}. * * @author Phillip Webb * @author Juergen Hoeller @@ -352,45 +357,8 @@ public class AnnotatedElementUtils { */ public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - return findAnnotationAttributes(element, annotationType, true, true, true, true, classValuesAsString, - nestedAnnotationsAsMap); - } - - /** - * Find the first annotation of the specified {@code annotationType} within - * the annotation hierarchy above the supplied {@code element} and - * merge that annotation's attributes with matching attributes from - * annotations in lower levels of the annotation hierarchy. - * - * @param element the annotated element; never {@code null} - * @param annotationType the fully qualified class name of the annotation - * type to find; never {@code null} or empty - * @param searchOnInterfaces whether to search on interfaces, if the - * annotated element is a class - * @param searchOnSuperclasses whether to search on superclasses, if - * the annotated element is a class - * @param searchOnMethodsInInterfaces whether to search on methods in - * interfaces, if the annotated element is a method - * @param searchOnMethodsInSuperclasses whether to search on methods - * in superclasses, if the annotated element is a method - * @param classValuesAsString whether to convert Class references into - * Strings or to preserve them as Class references - * @param nestedAnnotationsAsMap whether to convert nested Annotation - * instances into {@code AnnotationAttributes} maps or to preserve them - * as Annotation instances - * @return the merged {@code AnnotationAttributes}, or {@code null} if - * not found - * @since 4.2 - * @see #searchWithFindSemantics - * @see MergedAnnotationAttributesProcessor - */ - private static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType, - boolean searchOnInterfaces, boolean searchOnSuperclasses, boolean searchOnMethodsInInterfaces, - boolean searchOnMethodsInSuperclasses, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - - return searchWithFindSemantics(element, annotationType, searchOnInterfaces, searchOnSuperclasses, - searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, new MergedAnnotationAttributesProcessor( - annotationType, classValuesAsString, nestedAnnotationsAsMap)); + return searchWithFindSemantics(element, annotationType, new MergedAnnotationAttributesProcessor(annotationType, + classValuesAsString, nestedAnnotationsAsMap)); } /** @@ -511,32 +479,28 @@ public class AnnotatedElementUtils { if (visited.add(element)) { try { - // Local annotations: declared OR inherited - Annotation[] annotations = element.getAnnotations(); - // Search in local annotations - for (Annotation annotation : annotations) { - if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation) - && (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0)) { - T result = processor.process(annotation, metaDepth); - if (result != null) { - return result; - } - } + // Start searching within locally declared annotations + List declaredAnnotations = Arrays.asList(element.getDeclaredAnnotations()); + T result = searchWithGetSemanticsInAnnotations(declaredAnnotations, annotationType, processor, visited, + metaDepth); + if (result != null) { + return result; } - // Search in meta annotations on local annotations - for (Annotation annotation : annotations) { - if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) { - T result = searchWithGetSemantics(annotation.annotationType(), annotationType, processor, - visited, metaDepth + 1); - if (result != null) { - processor.postProcess(annotation, result); - return result; - } + List inheritedAnnotations = new ArrayList(); + for (Annotation annotation : element.getAnnotations()) { + if (!declaredAnnotations.contains(annotation)) { + inheritedAnnotations.add(annotation); } } + // Continue searching within inherited annotations + result = searchWithGetSemanticsInAnnotations(inheritedAnnotations, annotationType, processor, visited, + metaDepth); + if (result != null) { + return result; + } } catch (Exception ex) { AnnotationUtils.logIntrospectionFailure(element, ex); @@ -545,6 +509,69 @@ public class AnnotatedElementUtils { return null; } + /** + * This method is invoked by + * {@link #searchWithGetSemantics(AnnotatedElement, String, Processor, Set, int)} + * to perform the actual search within the supplied list of annotations. + *

This method should be invoked first with locally declared annotations + * and then subsequently with inherited annotations, thereby allowing + * local annotations to take precedence over inherited annotations. + * + *

The {@code metaDepth} parameter is explained in the + * {@link Processor#process process()} method of the {@link Processor} + * API. + * + * @param annotations the annotations to search in; never {@code null} + * @param annotationType the fully qualified class name of the annotation + * type to find; never {@code null} or empty + * @param processor the processor to delegate to + * @param visited the set of annotated elements that have already been visited + * @param metaDepth the meta-depth of the annotation + * @return the result of the processor, potentially {@code null} + */ + private static T searchWithGetSemanticsInAnnotations(List annotations, String annotationType, + Processor processor, Set visited, int metaDepth) { + + // Search in annotations + for (Annotation annotation : annotations) { + if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation) + && (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0)) { + T result = processor.process(annotation, metaDepth); + if (result != null) { + return result; + } + } + } + + // Recursively search in meta-annotations + for (Annotation annotation : annotations) { + if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) { + T result = searchWithGetSemantics(annotation.annotationType(), annotationType, processor, visited, + metaDepth + 1); + if (result != null) { + processor.postProcess(annotation, result); + return result; + } + } + } + + return null; + } + + /** + * Search for annotations of the specified {@code annotationType} on + * the specified {@code element}, following find semantics. + * + * @param element the annotated element; never {@code null} + * @param annotationType the fully qualified class name of the annotation + * type to find; never {@code null} or empty + * @param processor the processor to delegate to + * @return the result of the processor, potentially {@code null} + */ + private static T searchWithFindSemantics(AnnotatedElement element, String annotationType, Processor processor) { + return searchWithFindSemantics(element, annotationType, true, true, true, true, processor); + } + /** * Search for annotations of the specified {@code annotationType} on * the specified {@code element}, following find semantics. diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java index 28da1783eb..1f53718edc 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java @@ -23,16 +23,15 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; -import java.util.Arrays; import java.util.Set; import java.util.stream.Collectors; -import org.junit.Ignore; import org.junit.Test; import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; +import static java.util.Arrays.*; import static org.junit.Assert.*; import static org.springframework.core.annotation.AnnotatedElementUtils.*; @@ -45,8 +44,10 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.*; */ public class AnnotatedElementUtilsTests { + private static final String TX_NAME = Transactional.class.getName(); + private Set names(Class... classes) { - return Arrays.stream(classes).map(clazz -> clazz.getName()).collect(Collectors.toSet()); + return stream(classes).map(clazz -> clazz.getName()).collect(Collectors.toSet()); } @Test @@ -62,13 +63,14 @@ public class AnnotatedElementUtilsTests { @Test public void getMetaAnnotationTypesOnClassWithMetaDepth2() { - Set names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class); + Set names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class, + ComposedTransactionalComponent.class); assertEquals(names(TransactionalComponent.class, Transactional.class, Component.class), names); } @Test public void hasMetaAnnotationTypesOnNonAnnotatedClass() { - assertFalse(hasMetaAnnotationTypes(NonAnnotatedClass.class, Transactional.class.getName())); + assertFalse(hasMetaAnnotationTypes(NonAnnotatedClass.class, TX_NAME)); } @Test @@ -78,20 +80,21 @@ public class AnnotatedElementUtilsTests { @Test public void hasMetaAnnotationTypesOnClassWithMetaDepth1() { - assertTrue(hasMetaAnnotationTypes(TransactionalComponentClass.class, Transactional.class.getName())); + assertTrue(hasMetaAnnotationTypes(TransactionalComponentClass.class, TX_NAME)); assertTrue(hasMetaAnnotationTypes(TransactionalComponentClass.class, Component.class.getName())); } @Test public void hasMetaAnnotationTypesOnClassWithMetaDepth2() { - assertTrue(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, Transactional.class.getName())); + assertTrue(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, TX_NAME)); assertTrue(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, Component.class.getName())); - assertFalse(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class.getName())); + assertFalse(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, + ComposedTransactionalComponent.class.getName())); } @Test public void isAnnotatedOnNonAnnotatedClass() { - assertFalse(isAnnotated(NonAnnotatedClass.class, Transactional.class.getName())); + assertFalse(isAnnotated(NonAnnotatedClass.class, TX_NAME)); } @Test @@ -107,35 +110,49 @@ public class AnnotatedElementUtilsTests { @Test public void isAnnotatedOnClassWithMetaDepth1() { - assertTrue(isAnnotated(TransactionalComponentClass.class, Transactional.class.getName())); + assertTrue(isAnnotated(TransactionalComponentClass.class, TX_NAME)); assertTrue(isAnnotated(TransactionalComponentClass.class, Component.class.getName())); } @Test public void isAnnotatedOnClassWithMetaDepth2() { - assertTrue(isAnnotated(ComposedTransactionalComponentClass.class, Transactional.class.getName())); + assertTrue(isAnnotated(ComposedTransactionalComponentClass.class, TX_NAME)); assertTrue(isAnnotated(ComposedTransactionalComponentClass.class, Component.class.getName())); - assertTrue(isAnnotated(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class.getName())); + assertTrue(isAnnotated(ComposedTransactionalComponentClass.class, + ComposedTransactionalComponent.class.getName())); } @Test public void getAllAnnotationAttributesOnNonAnnotatedClass() { - assertNull(getAllAnnotationAttributes(NonAnnotatedClass.class, Transactional.class.getName())); + assertNull(getAllAnnotationAttributes(NonAnnotatedClass.class, TX_NAME)); } @Test public void getAllAnnotationAttributesOnClassWithLocalAnnotation() { - MultiValueMap attributes = getAllAnnotationAttributes(TxConfig.class, Transactional.class.getName()); + MultiValueMap attributes = getAllAnnotationAttributes(TxConfig.class, TX_NAME); assertNotNull("Annotation attributes map for @Transactional on TxConfig", attributes); - assertEquals("value for TxConfig.", Arrays.asList("TxConfig"), attributes.get("value")); + assertEquals("value for TxConfig.", asList("TxConfig"), attributes.get("value")); + } + + @Test + public void getAllAnnotationAttributesOnClassWithLocalComposedAnnotationAndInheritedAnnotation() { + MultiValueMap attributes = getAllAnnotationAttributes(SubClassWithInheritedAnnotation.class, TX_NAME); + assertNotNull("Annotation attributes map for @Transactional on SubClassWithInheritedAnnotation", attributes); + assertEquals(asList("composed2", "transactionManager"), attributes.get("qualifier")); + } + + @Test + public void getAllAnnotationAttributesFavorsInheritedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() { + MultiValueMap attributes = getAllAnnotationAttributes(SubSubClassWithInheritedAnnotation.class, TX_NAME); + assertNotNull("Annotation attributes map for @Transactional on SubSubClassWithInheritedAnnotation", attributes); + assertEquals(asList("transactionManager"), attributes.get("qualifier")); } @Test public void getAllAnnotationAttributesFavorsInheritedComposedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() { - MultiValueMap attributes = getAllAnnotationAttributes(SubSubClassWithInheritedComposedAnnotation.class, - Transactional.class.getName()); + MultiValueMap attributes = getAllAnnotationAttributes( SubSubClassWithInheritedComposedAnnotation.class, TX_NAME); assertNotNull("Annotation attributes map for @Transactional on SubSubClassWithInheritedComposedAnnotation", attributes); - assertEquals(Arrays.asList("composed1"), attributes.get("qualifier")); + assertEquals(asList("composed1"), attributes.get("qualifier")); } /** @@ -149,9 +166,9 @@ public class AnnotatedElementUtilsTests { */ @Test public void getAllAnnotationAttributesOnClassWithLocalAnnotationThatShadowsAnnotationFromSuperclass() { - MultiValueMap attributes = getAllAnnotationAttributes(DerivedTxConfig.class, Transactional.class.getName()); + MultiValueMap attributes = getAllAnnotationAttributes(DerivedTxConfig.class, TX_NAME); assertNotNull("Annotation attributes map for @Transactional on DerivedTxConfig", attributes); - assertEquals("value for DerivedTxConfig.", Arrays.asList("DerivedTxConfig"), attributes.get("value")); + assertEquals("value for DerivedTxConfig.", asList("DerivedTxConfig"), attributes.get("value")); } /** @@ -161,17 +178,15 @@ public class AnnotatedElementUtilsTests { */ @Test public void getAllAnnotationAttributesOnClassWithMultipleComposedAnnotations() { - MultiValueMap attributes = getAllAnnotationAttributes(TxFromMultipleComposedAnnotations.class, - Transactional.class.getName()); + MultiValueMap attributes = getAllAnnotationAttributes(TxFromMultipleComposedAnnotations.class, TX_NAME); assertNotNull("Annotation attributes map for @Transactional on TxFromMultipleComposedAnnotations", attributes); - assertEquals("value for TxFromMultipleComposedAnnotations.", Arrays.asList("TxComposed1", "TxComposed2"), - attributes.get("value")); + assertEquals("value for TxFromMultipleComposedAnnotations.", asList("TxComposed1", "TxComposed2"), attributes.get("value")); } @Test public void getAnnotationAttributesOnClassWithLocalAnnotation() { Class element = TxConfig.class; - String name = Transactional.class.getName(); + String name = TX_NAME; AnnotationAttributes attributes = getAnnotationAttributes(element, name); assertNotNull("Annotation attributes for @Transactional on TxConfig", attributes); assertEquals("value for TxConfig.", "TxConfig", attributes.getString("value")); @@ -182,7 +197,7 @@ public class AnnotatedElementUtilsTests { @Test public void getAnnotationAttributesOnClassWithLocalAnnotationThatShadowsAnnotationFromSuperclass() { Class element = DerivedTxConfig.class; - String name = Transactional.class.getName(); + String name = TX_NAME; AnnotationAttributes attributes = getAnnotationAttributes(element, name); assertNotNull("Annotation attributes for @Transactional on DerivedTxConfig", attributes); assertEquals("value for DerivedTxConfig.", "DerivedTxConfig", attributes.getString("value")); @@ -192,58 +207,59 @@ public class AnnotatedElementUtilsTests { @Test public void getAnnotationAttributesOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() { - AnnotationAttributes attributes = getAnnotationAttributes(MetaCycleAnnotatedClass.class, - Transactional.class.getName()); + AnnotationAttributes attributes = getAnnotationAttributes(MetaCycleAnnotatedClass.class, TX_NAME); assertNull("Should not find annotation attributes for @Transactional on MetaCycleAnnotatedClass", attributes); } + @Test + public void getAnnotationAttributesFavorsLocalComposedAnnotationOverInheritedAnnotation() { + Class element = SubClassWithInheritedAnnotation.class; + String name = TX_NAME; + AnnotationAttributes attributes = getAnnotationAttributes(element, name); + assertNotNull("AnnotationAttributes for @Transactional on SubClassWithInheritedAnnotation", attributes); + // Verify contracts between utility methods: + assertTrue(isAnnotated(element, name)); + assertTrue("readOnly flag for SubClassWithInheritedAnnotation.", attributes.getBoolean("readOnly")); + } + @Test public void getAnnotationAttributesFavorsInheritedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() { Class element = SubSubClassWithInheritedAnnotation.class; - String name = Transactional.class.getName(); + String name = TX_NAME; AnnotationAttributes attributes = getAnnotationAttributes(element, name); assertNotNull("AnnotationAttributes for @Transactional on SubSubClassWithInheritedAnnotation", attributes); // Verify contracts between utility methods: assertTrue(isAnnotated(element, name)); - - // TODO [SPR-11598] Set expected to true. - boolean expected = false; - assertEquals("readOnly flag for SubSubClassWithInheritedAnnotation.", expected, attributes.getBoolean("readOnly")); + assertFalse("readOnly flag for SubSubClassWithInheritedAnnotation.", attributes.getBoolean("readOnly")); } @Test public void getAnnotationAttributesFavorsInheritedComposedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() { Class element = SubSubClassWithInheritedComposedAnnotation.class; - String name = Transactional.class.getName(); + String name = TX_NAME; AnnotationAttributes attributes = getAnnotationAttributes(element, name); assertNotNull("AnnotationAttributtes for @Transactional on SubSubClassWithInheritedComposedAnnotation.", attributes); // Verify contracts between utility methods: assertTrue(isAnnotated(element, name)); - - // TODO [SPR-11598] Set expected to true. - boolean expected = false; - assertEquals("readOnly flag for SubSubClassWithInheritedComposedAnnotation.", expected, - attributes.getBoolean("readOnly")); + assertFalse("readOnly flag for SubSubClassWithInheritedComposedAnnotation.", attributes.getBoolean("readOnly")); } - // TODO [SPR-11598] Enable test. - @Ignore("Disabled until SPR-11598 is resolved") @Test public void getAnnotationAttributesFromInterfaceImplementedBySuperclass() { Class element = ConcreteClassWithInheritedAnnotation.class; - String name = Transactional.class.getName(); + String name = TX_NAME; AnnotationAttributes attributes = getAnnotationAttributes(element, name); - assertNotNull("Should find @Transactional on ConcreteClassWithInheritedAnnotation", attributes); + assertNull("Should not find @Transactional on ConcreteClassWithInheritedAnnotation", attributes); // Verify contracts between utility methods: - assertTrue(isAnnotated(element, name)); + assertFalse(isAnnotated(element, name)); } @Test public void getAnnotationAttributesOnInheritedAnnotationInterface() { Class element = InheritedAnnotationInterface.class; - String name = Transactional.class.getName(); + String name = TX_NAME; AnnotationAttributes attributes = getAnnotationAttributes(element, name); - assertNotNull("Should get @Transactional on InheritedAnnotationInterface", attributes); + assertNotNull("Should find @Transactional on InheritedAnnotationInterface", attributes); // Verify contracts between utility methods: assertTrue(isAnnotated(element, name)); } @@ -253,7 +269,7 @@ public class AnnotatedElementUtilsTests { Class element = NonInheritedAnnotationInterface.class; String name = Order.class.getName(); AnnotationAttributes attributes = getAnnotationAttributes(element, name); - assertNotNull("Should get @Order on NonInheritedAnnotationInterface", attributes); + assertNotNull("Should find @Order on NonInheritedAnnotationInterface", attributes); // Verify contracts between utility methods: assertTrue(isAnnotated(element, name)); } @@ -322,7 +338,7 @@ public class AnnotatedElementUtilsTests { public void findAnnotationAttributesInheritedFromBridgedMethod() throws NoSuchMethodException { Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handleParameterized", String.class); AnnotationAttributes attributes = findAnnotationAttributes(method, Transactional.class); - assertNull("Should not find @Transactional on bridged ConcreteClassWithInheritedAnnotation.handleParameterized() method", attributes); + assertNull("Should not find @Transactional on bridged ConcreteClassWithInheritedAnnotation.handleParameterized()", attributes); } /** @@ -390,7 +406,7 @@ public class AnnotatedElementUtilsTests { // ------------------------------------------------------------------------- @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.TYPE, ElementType.METHOD}) + @Target({ ElementType.TYPE, ElementType.METHOD }) @Documented @Inherited @interface Transactional { @@ -559,7 +575,6 @@ public class AnnotatedElementUtilsTests { } } - @Transactional public static interface InheritedAnnotationInterface { } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java index d34e0c20e9..cf4f7543d5 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java @@ -153,27 +153,24 @@ public class AnnotationUtilsTests { /** @since 4.1.2 */ @Test - public void findClassAnnotationFavorsLocalMetaAnnotationsOverInterfaces() { - Component component = AnnotationUtils.findAnnotation( - ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, Component.class); + public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverAnnotationsOnInterfaces() { + Component component = AnnotationUtils.findAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, Component.class); assertNotNull(component); assertEquals("meta2", component.value()); } /** @since 4.0.3 */ @Test - public void findClassAnnotationFavorsInheritedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() { - Transactional transactional = AnnotationUtils.findAnnotation( - SubSubClassWithInheritedAnnotation.class, Transactional.class); + public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverInheritedAnnotations() { + Transactional transactional = AnnotationUtils.findAnnotation(SubSubClassWithInheritedAnnotation.class, Transactional.class); assertNotNull(transactional); assertTrue("readOnly flag for SubSubClassWithInheritedAnnotation", transactional.readOnly()); } /** @since 4.0.3 */ @Test - public void findClassAnnotationFavorsInheritedComposedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() { - Component component = AnnotationUtils.findAnnotation( - SubSubClassWithInheritedMetaAnnotation.class, Component.class); + public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverInheritedComposedAnnotations() { + Component component = AnnotationUtils.findAnnotation(SubSubClassWithInheritedMetaAnnotation.class, Component.class); assertNotNull(component); assertEquals("meta2", component.value()); }