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
This commit is contained in:
parent
03ade48d68
commit
ebed52cc22
|
@ -19,8 +19,11 @@ package org.springframework.core.annotation;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -71,11 +74,13 @@ import org.springframework.util.MultiValueMap;
|
||||||
*
|
*
|
||||||
* <h3>Support for {@code @Inherited}</h3>
|
* <h3>Support for {@code @Inherited}</h3>
|
||||||
* <p>Methods following <em>get semantics</em> will honor the contract of
|
* <p>Methods following <em>get semantics</em> will honor the contract of
|
||||||
* Java's {@link java.lang.annotation.Inherited @Inherited} annotation.
|
* Java's {@link java.lang.annotation.Inherited @Inherited} annotation except
|
||||||
* However, methods following <em>find semantics</em> will ignore the
|
* that locally declared annotations (including custom composed annotations)
|
||||||
* presence of {@code @Inherited} since the <em>find</em> search algorithm
|
* will be favored over inherited annotations. In contrast, methods following
|
||||||
* manually traverses type and method hierarchies and thereby implicitly
|
* <em>find semantics</em> will completely ignore the presence of
|
||||||
* supports annotation inheritance without the need for {@code @Inherited}.
|
* {@code @Inherited} since the <em>find</em> search algorithm manually
|
||||||
|
* traverses type and method hierarchies and thereby implicitly supports
|
||||||
|
* annotation inheritance without the need for {@code @Inherited}.
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
@ -352,45 +357,8 @@ public class AnnotatedElementUtils {
|
||||||
*/
|
*/
|
||||||
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType,
|
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType,
|
||||||
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||||
return findAnnotationAttributes(element, annotationType, true, true, true, true, classValuesAsString,
|
return searchWithFindSemantics(element, annotationType, new MergedAnnotationAttributesProcessor(annotationType,
|
||||||
nestedAnnotationsAsMap);
|
classValuesAsString, nestedAnnotationsAsMap));
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the first annotation of the specified {@code annotationType} within
|
|
||||||
* the annotation hierarchy <em>above</em> the supplied {@code element} and
|
|
||||||
* merge that annotation's attributes with <em>matching</em> 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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -511,32 +479,28 @@ public class AnnotatedElementUtils {
|
||||||
|
|
||||||
if (visited.add(element)) {
|
if (visited.add(element)) {
|
||||||
try {
|
try {
|
||||||
// Local annotations: declared OR inherited
|
|
||||||
Annotation[] annotations = element.getAnnotations();
|
|
||||||
|
|
||||||
// Search in local annotations
|
// Start searching within locally declared annotations
|
||||||
for (Annotation annotation : annotations) {
|
List<Annotation> declaredAnnotations = Arrays.asList(element.getDeclaredAnnotations());
|
||||||
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)
|
T result = searchWithGetSemanticsInAnnotations(declaredAnnotations, annotationType, processor, visited,
|
||||||
&& (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0)) {
|
metaDepth);
|
||||||
T result = processor.process(annotation, metaDepth);
|
if (result != null) {
|
||||||
if (result != null) {
|
return result;
|
||||||
return result;
|
}
|
||||||
}
|
|
||||||
|
List<Annotation> inheritedAnnotations = new ArrayList<Annotation>();
|
||||||
|
for (Annotation annotation : element.getAnnotations()) {
|
||||||
|
if (!declaredAnnotations.contains(annotation)) {
|
||||||
|
inheritedAnnotations.add(annotation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search in meta annotations on local annotations
|
// Continue searching within inherited annotations
|
||||||
for (Annotation annotation : annotations) {
|
result = searchWithGetSemanticsInAnnotations(inheritedAnnotations, annotationType, processor, visited,
|
||||||
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
|
metaDepth);
|
||||||
T result = searchWithGetSemantics(annotation.annotationType(), annotationType, processor,
|
if (result != null) {
|
||||||
visited, metaDepth + 1);
|
return result;
|
||||||
if (result != null) {
|
|
||||||
processor.postProcess(annotation, result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
AnnotationUtils.logIntrospectionFailure(element, ex);
|
AnnotationUtils.logIntrospectionFailure(element, ex);
|
||||||
|
@ -545,6 +509,69 @@ public class AnnotatedElementUtils {
|
||||||
return null;
|
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.
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>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> T searchWithGetSemanticsInAnnotations(List<Annotation> annotations, String annotationType,
|
||||||
|
Processor<T> processor, Set<AnnotatedElement> 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 <em>find semantics</em>.
|
||||||
|
*
|
||||||
|
* @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> T searchWithFindSemantics(AnnotatedElement element, String annotationType, Processor<T> processor) {
|
||||||
|
return searchWithFindSemantics(element, annotationType, true, true, true, true, processor);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search for annotations of the specified {@code annotationType} on
|
* Search for annotations of the specified {@code annotationType} on
|
||||||
* the specified {@code element}, following <em>find semantics</em>.
|
* the specified {@code element}, following <em>find semantics</em>.
|
||||||
|
|
|
@ -23,16 +23,15 @@ import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
|
import static java.util.Arrays.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
|
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
|
||||||
|
|
||||||
|
@ -45,8 +44,10 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.*;
|
||||||
*/
|
*/
|
||||||
public class AnnotatedElementUtilsTests {
|
public class AnnotatedElementUtilsTests {
|
||||||
|
|
||||||
|
private static final String TX_NAME = Transactional.class.getName();
|
||||||
|
|
||||||
private Set<String> names(Class<?>... classes) {
|
private Set<String> 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
|
@Test
|
||||||
|
@ -62,13 +63,14 @@ public class AnnotatedElementUtilsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getMetaAnnotationTypesOnClassWithMetaDepth2() {
|
public void getMetaAnnotationTypesOnClassWithMetaDepth2() {
|
||||||
Set<String> names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class);
|
Set<String> names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class,
|
||||||
|
ComposedTransactionalComponent.class);
|
||||||
assertEquals(names(TransactionalComponent.class, Transactional.class, Component.class), names);
|
assertEquals(names(TransactionalComponent.class, Transactional.class, Component.class), names);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void hasMetaAnnotationTypesOnNonAnnotatedClass() {
|
public void hasMetaAnnotationTypesOnNonAnnotatedClass() {
|
||||||
assertFalse(hasMetaAnnotationTypes(NonAnnotatedClass.class, Transactional.class.getName()));
|
assertFalse(hasMetaAnnotationTypes(NonAnnotatedClass.class, TX_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -78,20 +80,21 @@ public class AnnotatedElementUtilsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void hasMetaAnnotationTypesOnClassWithMetaDepth1() {
|
public void hasMetaAnnotationTypesOnClassWithMetaDepth1() {
|
||||||
assertTrue(hasMetaAnnotationTypes(TransactionalComponentClass.class, Transactional.class.getName()));
|
assertTrue(hasMetaAnnotationTypes(TransactionalComponentClass.class, TX_NAME));
|
||||||
assertTrue(hasMetaAnnotationTypes(TransactionalComponentClass.class, Component.class.getName()));
|
assertTrue(hasMetaAnnotationTypes(TransactionalComponentClass.class, Component.class.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void hasMetaAnnotationTypesOnClassWithMetaDepth2() {
|
public void hasMetaAnnotationTypesOnClassWithMetaDepth2() {
|
||||||
assertTrue(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, Transactional.class.getName()));
|
assertTrue(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, TX_NAME));
|
||||||
assertTrue(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, Component.class.getName()));
|
assertTrue(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, Component.class.getName()));
|
||||||
assertFalse(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class.getName()));
|
assertFalse(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class,
|
||||||
|
ComposedTransactionalComponent.class.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void isAnnotatedOnNonAnnotatedClass() {
|
public void isAnnotatedOnNonAnnotatedClass() {
|
||||||
assertFalse(isAnnotated(NonAnnotatedClass.class, Transactional.class.getName()));
|
assertFalse(isAnnotated(NonAnnotatedClass.class, TX_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -107,35 +110,49 @@ public class AnnotatedElementUtilsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void isAnnotatedOnClassWithMetaDepth1() {
|
public void isAnnotatedOnClassWithMetaDepth1() {
|
||||||
assertTrue(isAnnotated(TransactionalComponentClass.class, Transactional.class.getName()));
|
assertTrue(isAnnotated(TransactionalComponentClass.class, TX_NAME));
|
||||||
assertTrue(isAnnotated(TransactionalComponentClass.class, Component.class.getName()));
|
assertTrue(isAnnotated(TransactionalComponentClass.class, Component.class.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void isAnnotatedOnClassWithMetaDepth2() {
|
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, Component.class.getName()));
|
||||||
assertTrue(isAnnotated(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class.getName()));
|
assertTrue(isAnnotated(ComposedTransactionalComponentClass.class,
|
||||||
|
ComposedTransactionalComponent.class.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAllAnnotationAttributesOnNonAnnotatedClass() {
|
public void getAllAnnotationAttributesOnNonAnnotatedClass() {
|
||||||
assertNull(getAllAnnotationAttributes(NonAnnotatedClass.class, Transactional.class.getName()));
|
assertNull(getAllAnnotationAttributes(NonAnnotatedClass.class, TX_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAllAnnotationAttributesOnClassWithLocalAnnotation() {
|
public void getAllAnnotationAttributesOnClassWithLocalAnnotation() {
|
||||||
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(TxConfig.class, Transactional.class.getName());
|
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(TxConfig.class, TX_NAME);
|
||||||
assertNotNull("Annotation attributes map for @Transactional on TxConfig", attributes);
|
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<String, Object> 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<String, Object> attributes = getAllAnnotationAttributes(SubSubClassWithInheritedAnnotation.class, TX_NAME);
|
||||||
|
assertNotNull("Annotation attributes map for @Transactional on SubSubClassWithInheritedAnnotation", attributes);
|
||||||
|
assertEquals(asList("transactionManager"), attributes.get("qualifier"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAllAnnotationAttributesFavorsInheritedComposedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() {
|
public void getAllAnnotationAttributesFavorsInheritedComposedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() {
|
||||||
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(SubSubClassWithInheritedComposedAnnotation.class,
|
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes( SubSubClassWithInheritedComposedAnnotation.class, TX_NAME);
|
||||||
Transactional.class.getName());
|
|
||||||
assertNotNull("Annotation attributes map for @Transactional on SubSubClassWithInheritedComposedAnnotation", attributes);
|
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
|
@Test
|
||||||
public void getAllAnnotationAttributesOnClassWithLocalAnnotationThatShadowsAnnotationFromSuperclass() {
|
public void getAllAnnotationAttributesOnClassWithLocalAnnotationThatShadowsAnnotationFromSuperclass() {
|
||||||
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(DerivedTxConfig.class, Transactional.class.getName());
|
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(DerivedTxConfig.class, TX_NAME);
|
||||||
assertNotNull("Annotation attributes map for @Transactional on DerivedTxConfig", attributes);
|
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
|
@Test
|
||||||
public void getAllAnnotationAttributesOnClassWithMultipleComposedAnnotations() {
|
public void getAllAnnotationAttributesOnClassWithMultipleComposedAnnotations() {
|
||||||
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(TxFromMultipleComposedAnnotations.class,
|
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(TxFromMultipleComposedAnnotations.class, TX_NAME);
|
||||||
Transactional.class.getName());
|
|
||||||
assertNotNull("Annotation attributes map for @Transactional on TxFromMultipleComposedAnnotations", attributes);
|
assertNotNull("Annotation attributes map for @Transactional on TxFromMultipleComposedAnnotations", attributes);
|
||||||
assertEquals("value for TxFromMultipleComposedAnnotations.", Arrays.asList("TxComposed1", "TxComposed2"),
|
assertEquals("value for TxFromMultipleComposedAnnotations.", asList("TxComposed1", "TxComposed2"), attributes.get("value"));
|
||||||
attributes.get("value"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAnnotationAttributesOnClassWithLocalAnnotation() {
|
public void getAnnotationAttributesOnClassWithLocalAnnotation() {
|
||||||
Class<?> element = TxConfig.class;
|
Class<?> element = TxConfig.class;
|
||||||
String name = Transactional.class.getName();
|
String name = TX_NAME;
|
||||||
AnnotationAttributes attributes = getAnnotationAttributes(element, name);
|
AnnotationAttributes attributes = getAnnotationAttributes(element, name);
|
||||||
assertNotNull("Annotation attributes for @Transactional on TxConfig", attributes);
|
assertNotNull("Annotation attributes for @Transactional on TxConfig", attributes);
|
||||||
assertEquals("value for TxConfig.", "TxConfig", attributes.getString("value"));
|
assertEquals("value for TxConfig.", "TxConfig", attributes.getString("value"));
|
||||||
|
@ -182,7 +197,7 @@ public class AnnotatedElementUtilsTests {
|
||||||
@Test
|
@Test
|
||||||
public void getAnnotationAttributesOnClassWithLocalAnnotationThatShadowsAnnotationFromSuperclass() {
|
public void getAnnotationAttributesOnClassWithLocalAnnotationThatShadowsAnnotationFromSuperclass() {
|
||||||
Class<?> element = DerivedTxConfig.class;
|
Class<?> element = DerivedTxConfig.class;
|
||||||
String name = Transactional.class.getName();
|
String name = TX_NAME;
|
||||||
AnnotationAttributes attributes = getAnnotationAttributes(element, name);
|
AnnotationAttributes attributes = getAnnotationAttributes(element, name);
|
||||||
assertNotNull("Annotation attributes for @Transactional on DerivedTxConfig", attributes);
|
assertNotNull("Annotation attributes for @Transactional on DerivedTxConfig", attributes);
|
||||||
assertEquals("value for DerivedTxConfig.", "DerivedTxConfig", attributes.getString("value"));
|
assertEquals("value for DerivedTxConfig.", "DerivedTxConfig", attributes.getString("value"));
|
||||||
|
@ -192,58 +207,59 @@ public class AnnotatedElementUtilsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAnnotationAttributesOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() {
|
public void getAnnotationAttributesOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() {
|
||||||
AnnotationAttributes attributes = getAnnotationAttributes(MetaCycleAnnotatedClass.class,
|
AnnotationAttributes attributes = getAnnotationAttributes(MetaCycleAnnotatedClass.class, TX_NAME);
|
||||||
Transactional.class.getName());
|
|
||||||
assertNull("Should not find annotation attributes for @Transactional on MetaCycleAnnotatedClass", attributes);
|
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
|
@Test
|
||||||
public void getAnnotationAttributesFavorsInheritedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() {
|
public void getAnnotationAttributesFavorsInheritedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() {
|
||||||
Class<?> element = SubSubClassWithInheritedAnnotation.class;
|
Class<?> element = SubSubClassWithInheritedAnnotation.class;
|
||||||
String name = Transactional.class.getName();
|
String name = TX_NAME;
|
||||||
AnnotationAttributes attributes = getAnnotationAttributes(element, name);
|
AnnotationAttributes attributes = getAnnotationAttributes(element, name);
|
||||||
assertNotNull("AnnotationAttributes for @Transactional on SubSubClassWithInheritedAnnotation", attributes);
|
assertNotNull("AnnotationAttributes for @Transactional on SubSubClassWithInheritedAnnotation", attributes);
|
||||||
// Verify contracts between utility methods:
|
// Verify contracts between utility methods:
|
||||||
assertTrue(isAnnotated(element, name));
|
assertTrue(isAnnotated(element, name));
|
||||||
|
assertFalse("readOnly flag for SubSubClassWithInheritedAnnotation.", attributes.getBoolean("readOnly"));
|
||||||
// TODO [SPR-11598] Set expected to true.
|
|
||||||
boolean expected = false;
|
|
||||||
assertEquals("readOnly flag for SubSubClassWithInheritedAnnotation.", expected, attributes.getBoolean("readOnly"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAnnotationAttributesFavorsInheritedComposedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() {
|
public void getAnnotationAttributesFavorsInheritedComposedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() {
|
||||||
Class<?> element = SubSubClassWithInheritedComposedAnnotation.class;
|
Class<?> element = SubSubClassWithInheritedComposedAnnotation.class;
|
||||||
String name = Transactional.class.getName();
|
String name = TX_NAME;
|
||||||
AnnotationAttributes attributes = getAnnotationAttributes(element, name);
|
AnnotationAttributes attributes = getAnnotationAttributes(element, name);
|
||||||
assertNotNull("AnnotationAttributtes for @Transactional on SubSubClassWithInheritedComposedAnnotation.", attributes);
|
assertNotNull("AnnotationAttributtes for @Transactional on SubSubClassWithInheritedComposedAnnotation.", attributes);
|
||||||
// Verify contracts between utility methods:
|
// Verify contracts between utility methods:
|
||||||
assertTrue(isAnnotated(element, name));
|
assertTrue(isAnnotated(element, name));
|
||||||
|
assertFalse("readOnly flag for SubSubClassWithInheritedComposedAnnotation.", attributes.getBoolean("readOnly"));
|
||||||
// TODO [SPR-11598] Set expected to true.
|
|
||||||
boolean expected = false;
|
|
||||||
assertEquals("readOnly flag for SubSubClassWithInheritedComposedAnnotation.", expected,
|
|
||||||
attributes.getBoolean("readOnly"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO [SPR-11598] Enable test.
|
|
||||||
@Ignore("Disabled until SPR-11598 is resolved")
|
|
||||||
@Test
|
@Test
|
||||||
public void getAnnotationAttributesFromInterfaceImplementedBySuperclass() {
|
public void getAnnotationAttributesFromInterfaceImplementedBySuperclass() {
|
||||||
Class<?> element = ConcreteClassWithInheritedAnnotation.class;
|
Class<?> element = ConcreteClassWithInheritedAnnotation.class;
|
||||||
String name = Transactional.class.getName();
|
String name = TX_NAME;
|
||||||
AnnotationAttributes attributes = getAnnotationAttributes(element, 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:
|
// Verify contracts between utility methods:
|
||||||
assertTrue(isAnnotated(element, name));
|
assertFalse(isAnnotated(element, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAnnotationAttributesOnInheritedAnnotationInterface() {
|
public void getAnnotationAttributesOnInheritedAnnotationInterface() {
|
||||||
Class<?> element = InheritedAnnotationInterface.class;
|
Class<?> element = InheritedAnnotationInterface.class;
|
||||||
String name = Transactional.class.getName();
|
String name = TX_NAME;
|
||||||
AnnotationAttributes attributes = getAnnotationAttributes(element, 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:
|
// Verify contracts between utility methods:
|
||||||
assertTrue(isAnnotated(element, name));
|
assertTrue(isAnnotated(element, name));
|
||||||
}
|
}
|
||||||
|
@ -253,7 +269,7 @@ public class AnnotatedElementUtilsTests {
|
||||||
Class<?> element = NonInheritedAnnotationInterface.class;
|
Class<?> element = NonInheritedAnnotationInterface.class;
|
||||||
String name = Order.class.getName();
|
String name = Order.class.getName();
|
||||||
AnnotationAttributes attributes = getAnnotationAttributes(element, name);
|
AnnotationAttributes attributes = getAnnotationAttributes(element, name);
|
||||||
assertNotNull("Should get @Order on NonInheritedAnnotationInterface", attributes);
|
assertNotNull("Should find @Order on NonInheritedAnnotationInterface", attributes);
|
||||||
// Verify contracts between utility methods:
|
// Verify contracts between utility methods:
|
||||||
assertTrue(isAnnotated(element, name));
|
assertTrue(isAnnotated(element, name));
|
||||||
}
|
}
|
||||||
|
@ -322,7 +338,7 @@ public class AnnotatedElementUtilsTests {
|
||||||
public void findAnnotationAttributesInheritedFromBridgedMethod() throws NoSuchMethodException {
|
public void findAnnotationAttributesInheritedFromBridgedMethod() throws NoSuchMethodException {
|
||||||
Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handleParameterized", String.class);
|
Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handleParameterized", String.class);
|
||||||
AnnotationAttributes attributes = findAnnotationAttributes(method, Transactional.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)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||||
@Documented
|
@Documented
|
||||||
@Inherited
|
@Inherited
|
||||||
@interface Transactional {
|
@interface Transactional {
|
||||||
|
@ -559,7 +575,6 @@ public class AnnotatedElementUtilsTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public static interface InheritedAnnotationInterface {
|
public static interface InheritedAnnotationInterface {
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,27 +153,24 @@ public class AnnotationUtilsTests {
|
||||||
|
|
||||||
/** @since 4.1.2 */
|
/** @since 4.1.2 */
|
||||||
@Test
|
@Test
|
||||||
public void findClassAnnotationFavorsLocalMetaAnnotationsOverInterfaces() {
|
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverAnnotationsOnInterfaces() {
|
||||||
Component component = AnnotationUtils.findAnnotation(
|
Component component = AnnotationUtils.findAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, Component.class);
|
||||||
ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, Component.class);
|
|
||||||
assertNotNull(component);
|
assertNotNull(component);
|
||||||
assertEquals("meta2", component.value());
|
assertEquals("meta2", component.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @since 4.0.3 */
|
/** @since 4.0.3 */
|
||||||
@Test
|
@Test
|
||||||
public void findClassAnnotationFavorsInheritedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() {
|
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverInheritedAnnotations() {
|
||||||
Transactional transactional = AnnotationUtils.findAnnotation(
|
Transactional transactional = AnnotationUtils.findAnnotation(SubSubClassWithInheritedAnnotation.class, Transactional.class);
|
||||||
SubSubClassWithInheritedAnnotation.class, Transactional.class);
|
|
||||||
assertNotNull(transactional);
|
assertNotNull(transactional);
|
||||||
assertTrue("readOnly flag for SubSubClassWithInheritedAnnotation", transactional.readOnly());
|
assertTrue("readOnly flag for SubSubClassWithInheritedAnnotation", transactional.readOnly());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @since 4.0.3 */
|
/** @since 4.0.3 */
|
||||||
@Test
|
@Test
|
||||||
public void findClassAnnotationFavorsInheritedComposedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() {
|
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverInheritedComposedAnnotations() {
|
||||||
Component component = AnnotationUtils.findAnnotation(
|
Component component = AnnotationUtils.findAnnotation(SubSubClassWithInheritedMetaAnnotation.class, Component.class);
|
||||||
SubSubClassWithInheritedMetaAnnotation.class, Component.class);
|
|
||||||
assertNotNull(component);
|
assertNotNull(component);
|
||||||
assertEquals("meta2", component.value());
|
assertEquals("meta2", component.value());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue