Skip to content

Commit 4521a79

Browse files
committed
Find annotations on implemented generic superclass methods as well
Includes Java 8 getDeclaredAnnotation shortcut for lookup on Class. Issue: SPR-17146
1 parent fa72186 commit 4521a79

File tree

4 files changed

+92
-78
lines changed

4 files changed

+92
-78
lines changed

spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java

Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -386,13 +386,14 @@ public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElemen
386386
@Nullable
387387
public static <A extends Annotation> A getMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
388388
// Shortcut: directly present on the element, with no merging needed?
389-
if (!(element instanceof Class)) {
390-
// Do not use this shortcut against a Class: Inherited annotations
391-
// would get preferred over locally declared composed annotations.
392-
A annotation = element.getAnnotation(annotationType);
393-
if (annotation != null) {
394-
return AnnotationUtils.synthesizeAnnotation(annotation, element);
395-
}
389+
A annotation = element.getDeclaredAnnotation(annotationType);
390+
if (annotation != null) {
391+
return AnnotationUtils.synthesizeAnnotation(annotation, element);
392+
}
393+
394+
// Shortcut: no non-java annotations to be found on plain Java classes and org.springframework.lang types...
395+
if (AnnotationUtils.hasPlainJavaAnnotationsOnly(element) && !annotationType.getName().startsWith("java")) {
396+
return null;
396397
}
397398

398399
// Exhaustive retrieval of merged annotation attributes...
@@ -671,13 +672,9 @@ public static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedEleme
671672
@Nullable
672673
public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
673674
// Shortcut: directly present on the element, with no merging needed?
674-
if (!(element instanceof Class)) {
675-
// Do not use this shortcut against a Class: Inherited annotations
676-
// would get preferred over locally declared composed annotations.
677-
A annotation = element.getAnnotation(annotationType);
678-
if (annotation != null) {
679-
return AnnotationUtils.synthesizeAnnotation(annotation, element);
680-
}
675+
A annotation = element.getDeclaredAnnotation(annotationType);
676+
if (annotation != null) {
677+
return AnnotationUtils.synthesizeAnnotation(annotation, element);
681678
}
682679

683680
// Shortcut: no non-java annotations to be found on plain Java classes and org.springframework.lang types...
@@ -1145,8 +1142,7 @@ else if (currentAnnotationType == containerType) {
11451142
Set<Method> annotatedMethods = AnnotationUtils.getAnnotatedMethodsInBaseType(clazz);
11461143
if (!annotatedMethods.isEmpty()) {
11471144
for (Method annotatedMethod : annotatedMethods) {
1148-
if (annotatedMethod.getName().equals(method.getName()) &&
1149-
Arrays.equals(annotatedMethod.getParameterTypes(), method.getParameterTypes())) {
1145+
if (AnnotationUtils.isOverride(method, annotatedMethod)) {
11501146
Method resolvedSuperMethod = BridgeMethodResolver.findBridgedMethod(annotatedMethod);
11511147
result = searchWithFindSemantics(resolvedSuperMethod, annotationType, annotationName,
11521148
containerType, processor, visited, metaDepth);
@@ -1203,8 +1199,7 @@ private static <T> T searchOnInterfaces(Method method, @Nullable Class<? extends
12031199
Set<Method> annotatedMethods = AnnotationUtils.getAnnotatedMethodsInBaseType(ifc);
12041200
if (!annotatedMethods.isEmpty()) {
12051201
for (Method annotatedMethod : annotatedMethods) {
1206-
if (annotatedMethod.getName().equals(method.getName()) &&
1207-
Arrays.equals(annotatedMethod.getParameterTypes(), method.getParameterTypes())) {
1202+
if (AnnotationUtils.isOverride(method, annotatedMethod)) {
12081203
T result = searchWithFindSemantics(annotatedMethod, annotationType, annotationName,
12091204
containerType, processor, visited, metaDepth);
12101205
if (result != null) {
@@ -1280,7 +1275,7 @@ private static Class<? extends Annotation> resolveContainerType(Class<? extends
12801275

12811276
/**
12821277
* Validate that the supplied {@code containerType} is a proper container
1283-
* annotation for the supplied repeatable {@code annotationType} (i.e.,
1278+
* annotation for the supplied repeatable {@code annotationType} (i.e.
12841279
* that it declares a {@code value} attribute that holds an array of the
12851280
* {@code annotationType}).
12861281
* @throws AnnotationConfigurationException if the supplied {@code containerType}
@@ -1322,27 +1317,24 @@ private static <A extends Annotation> Set<A> postProcessAndSynthesizeAggregatedR
13221317

13231318
/**
13241319
* Callback interface that is used to process annotations during a search.
1325-
* <p>Depending on the use case, a processor may choose to
1326-
* {@linkplain #process} a single target annotation, multiple target
1327-
* annotations, or all annotations discovered by the currently executing
1328-
* search. The term "target" in this context refers to a matching
1329-
* annotation (i.e., a specific annotation type that was found during
1330-
* the search).
1331-
* <p>Returning a non-null value from the {@link #process}
1332-
* method instructs the search algorithm to stop searching further;
1333-
* whereas, returning {@code null} from the {@link #process} method
1334-
* instructs the search algorithm to continue searching for additional
1335-
* annotations. One exception to this rule applies to processors
1336-
* that {@linkplain #aggregates aggregate} results. If an aggregating
1337-
* processor returns a non-null value, that value will be added to the
1338-
* list of {@linkplain #getAggregatedResults aggregated results}
1320+
* <p>Depending on the use case, a processor may choose to {@linkplain #process}
1321+
* a single target annotation, multiple target annotations, or all annotations
1322+
* discovered by the currently executing search. The term "target" in this
1323+
* context refers to a matching annotation (i.e. a specific annotation type
1324+
* that was found during the search).
1325+
* <p>Returning a non-null value from the {@link #process} method instructs
1326+
* the search algorithm to stop searching further; whereas, returning
1327+
* {@code null} from the {@link #process} method instructs the search
1328+
* algorithm to continue searching for additional annotations. One exception
1329+
* to this rule applies to processors that {@linkplain #aggregates aggregate}
1330+
* results. If an aggregating processor returns a non-null value, that value
1331+
* will be added to the {@linkplain #getAggregatedResults aggregated results}
13391332
* and the search algorithm will continue.
1340-
* <p>Processors can optionally {@linkplain #postProcess post-process}
1341-
* the result of the {@link #process} method as the search algorithm
1342-
* goes back down the annotation hierarchy from an invocation of
1343-
* {@link #process} that returned a non-null value down to the
1344-
* {@link AnnotatedElement} that was supplied as the starting point to
1345-
* the search algorithm.
1333+
* <p>Processors can optionally {@linkplain #postProcess post-process} the
1334+
* result of the {@link #process} method as the search algorithm goes back
1335+
* down the annotation hierarchy from an invocation of {@link #process} that
1336+
* returned a non-null value down to the {@link AnnotatedElement} that was
1337+
* supplied as the starting point to the search algorithm.
13461338
* @param <T> the type of result returned by the processor
13471339
*/
13481340
private interface Processor<T> {

spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ private static <A extends Annotation> A findAnnotation(
520520

521521
/**
522522
* Find a single {@link Annotation} of {@code annotationType} on the supplied
523-
* {@link Method}, traversing its super methods (i.e., from superclasses and
523+
* {@link Method}, traversing its super methods (i.e. from superclasses and
524524
* interfaces) if the annotation is not <em>directly present</em> on the given
525525
* method itself.
526526
* <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
@@ -560,8 +560,7 @@ public static <A extends Annotation> A findAnnotation(Method method, @Nullable C
560560
Set<Method> annotatedMethods = getAnnotatedMethodsInBaseType(clazz);
561561
if (!annotatedMethods.isEmpty()) {
562562
for (Method annotatedMethod : annotatedMethods) {
563-
if (annotatedMethod.getName().equals(method.getName()) &&
564-
Arrays.equals(annotatedMethod.getParameterTypes(), method.getParameterTypes())) {
563+
if (isOverride(method, annotatedMethod)) {
565564
Method resolvedSuperMethod = BridgeMethodResolver.findBridgedMethod(annotatedMethod);
566565
result = findAnnotation((AnnotatedElement) resolvedSuperMethod, annotationType);
567566
if (result != null) {
@@ -650,7 +649,7 @@ private static boolean hasSearchableAnnotations(Method ifcMethod) {
650649
return false;
651650
}
652651

653-
private static boolean isOverride(Method method, Method candidate) {
652+
static boolean isOverride(Method method, Method candidate) {
654653
if (!candidate.getName().equals(method.getName()) ||
655654
candidate.getParameterCount() != method.getParameterCount()) {
656655
return false;
@@ -843,7 +842,7 @@ public static Class<?> findAnnotationDeclaringClassForTypes(List<Class<? extends
843842

844843
/**
845844
* Determine whether an annotation of the specified {@code annotationType}
846-
* is declared locally (i.e., <em>directly present</em>) on the supplied
845+
* is declared locally (i.e. <em>directly present</em>) on the supplied
847846
* {@code clazz}.
848847
* <p>The supplied {@link Class} may represent any type.
849848
* <p>Meta-annotations will <em>not</em> be searched.
@@ -872,8 +871,8 @@ public static boolean isAnnotationDeclaredLocally(Class<? extends Annotation> an
872871
/**
873872
* Determine whether an annotation of the specified {@code annotationType}
874873
* is <em>present</em> on the supplied {@code clazz} and is
875-
* {@linkplain java.lang.annotation.Inherited inherited} (i.e., not
876-
* <em>directly present</em>).
874+
* {@linkplain java.lang.annotation.Inherited inherited}
875+
* (i.e. not <em>directly present</em>).
877876
* <p>Meta-annotations will <em>not</em> be searched.
878877
* <p>If the supplied {@code clazz} is an interface, only the interface
879878
* itself will be checked. In accordance with standard meta-annotation
@@ -1681,10 +1680,10 @@ static <A extends Annotation> A[] synthesizeAnnotationArray(
16811680
* in the supplied annotation type.
16821681
* <p>The map is keyed by attribute name with each value representing
16831682
* a list of names of aliased attributes.
1684-
* <p>For <em>explicit</em> alias pairs such as x and y (i.e., where x
1683+
* <p>For <em>explicit</em> alias pairs such as x and y (i.e. where x
16851684
* is an {@code @AliasFor("y")} and y is an {@code @AliasFor("x")}, there
16861685
* will be two entries in the map: {@code x -> (y)} and {@code y -> (x)}.
1687-
* <p>For <em>implicit</em> aliases (i.e., attributes that are declared
1686+
* <p>For <em>implicit</em> aliases (i.e. attributes that are declared
16881687
* as attribute overrides for the same attribute in the same meta-annotation),
16891688
* there will be n entries in the map. For example, if x, y, and z are
16901689
* implicit aliases, the map will contain the following entries:
@@ -1733,7 +1732,7 @@ private static boolean canExposeSynthesizedMarker(Class<? extends Annotation> an
17331732

17341733
/**
17351734
* Determine if annotations of the supplied {@code annotationType} are
1736-
* <em>synthesizable</em> (i.e., in need of being wrapped in a dynamic
1735+
* <em>synthesizable</em> (i.e. in need of being wrapped in a dynamic
17371736
* proxy that provides functionality above that of a standard JDK
17381737
* annotation).
17391738
* <p>Specifically, an annotation is <em>synthesizable</em> if it declares

spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -525,19 +525,11 @@ public void findMergedAnnotationAttributesInheritedFromAbstractMethod() throws N
525525
assertNotNull("Should find @Transactional on ConcreteClassWithInheritedAnnotation.handle() method", attributes);
526526
}
527527

528-
/**
529-
* <p>{@code AbstractClassWithInheritedAnnotation} declares {@code handleParameterized(T)}; whereas,
530-
* {@code ConcreteClassWithInheritedAnnotation} declares {@code handleParameterized(String)}.
531-
* <p>As of Spring 4.2, {@code AnnotatedElementUtils.processWithFindSemantics()} does not resolve an
532-
* <em>equivalent</em> method in {@code AbstractClassWithInheritedAnnotation} for the <em>bridged</em>
533-
* {@code handleParameterized(String)} method.
534-
* @since 4.2
535-
*/
536528
@Test
537529
public void findMergedAnnotationAttributesInheritedFromBridgedMethod() throws NoSuchMethodException {
538530
Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handleParameterized", String.class);
539531
AnnotationAttributes attributes = findMergedAnnotationAttributes(method, Transactional.class);
540-
assertNull("Should not find @Transactional on bridged ConcreteClassWithInheritedAnnotation.handleParameterized()", attributes);
532+
assertNotNull("Should find @Transactional on bridged ConcreteClassWithInheritedAnnotation.handleParameterized()", attributes);
541533
}
542534

543535
/**
@@ -546,7 +538,7 @@ public void findMergedAnnotationAttributesInheritedFromBridgedMethod() throws No
546538
* @since 4.2
547539
*/
548540
@Test
549-
public void findMergedAnnotationAttributesFromBridgeMethod() throws NoSuchMethodException {
541+
public void findMergedAnnotationAttributesFromBridgeMethod() {
550542
Method[] methods = StringGenericParameter.class.getMethods();
551543
Method bridgeMethod = null;
552544
Method bridgedMethod = null;
@@ -733,6 +725,20 @@ public void findAllMergedAnnotationsOnClassWithInterface() throws Exception {
733725
assertEquals(1, allMergedAnnotations.size());
734726
}
735727

728+
@Test // SPR-16060
729+
public void findMethodAnnotationFromGenericInterface() throws Exception {
730+
Method method = ImplementsInterfaceWithGenericAnnotatedMethod.class.getMethod("foo", String.class);
731+
Order order = findMergedAnnotation(method, Order.class);
732+
assertNotNull(order);
733+
}
734+
735+
@Test // SPR-17146
736+
public void findMethodAnnotationFromGenericSuperclass() throws Exception {
737+
Method method = ExtendsBaseClassWithGenericAnnotatedMethod.class.getMethod("foo", String.class);
738+
Order order = findMergedAnnotation(method, Order.class);
739+
assertNotNull(order);
740+
}
741+
736742

737743
// -------------------------------------------------------------------------
738744

spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,7 @@ public void findMethodAnnotationOnBridgedMethod() throws Exception {
167167

168168
assertNull(bridgedMethod.getAnnotation(Order.class));
169169
assertNull(getAnnotation(bridgedMethod, Order.class));
170-
// AnnotationUtils.findAnnotation(Method, Class<A>) will not find an annotation on
171-
// the bridge method for a bridged method.
172-
assertNull(findAnnotation(bridgedMethod, Order.class));
170+
assertNotNull(findAnnotation(bridgedMethod, Order.class));
173171

174172
assertNotNull(bridgedMethod.getAnnotation(Transactional.class));
175173
assertNotNull(getAnnotation(bridgedMethod, Transactional.class));
@@ -190,6 +188,13 @@ public void findMethodAnnotationFromGenericInterface() throws Exception {
190188
assertNotNull(order);
191189
}
192190

191+
@Test // SPR-17146
192+
public void findMethodAnnotationFromGenericSuperclass() throws Exception {
193+
Method method = ExtendsBaseClassWithGenericAnnotatedMethod.class.getMethod("foo", String.class);
194+
Order order = findAnnotation(method, Order.class);
195+
assertNotNull(order);
196+
}
197+
193198
@Test
194199
public void findMethodAnnotationFromInterfaceOnSuper() throws Exception {
195200
Method method = SubOfImplementsInterfaceWithAnnotatedMethod.class.getMethod("foo");
@@ -204,23 +209,23 @@ public void findMethodAnnotationFromInterfaceWhenSuperDoesNotImplementMethod() t
204209
assertNotNull(order);
205210
}
206211

207-
/** @since 4.1.2 */
212+
// @since 4.1.2
208213
@Test
209214
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverAnnotationsOnInterfaces() {
210215
Component component = findAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, Component.class);
211216
assertNotNull(component);
212217
assertEquals("meta2", component.value());
213218
}
214219

215-
/** @since 4.0.3 */
220+
// @since 4.0.3
216221
@Test
217222
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverInheritedAnnotations() {
218223
Transactional transactional = findAnnotation(SubSubClassWithInheritedAnnotation.class, Transactional.class);
219224
assertNotNull(transactional);
220225
assertTrue("readOnly flag for SubSubClassWithInheritedAnnotation", transactional.readOnly());
221226
}
222227

223-
/** @since 4.0.3 */
228+
// @since 4.0.3
224229
@Test
225230
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverInheritedComposedAnnotations() {
226231
Component component = findAnnotation(SubSubClassWithInheritedMetaAnnotation.class, Component.class);
@@ -1762,18 +1767,6 @@ public static class TransactionalAndOrderedClass extends TransactionalClass {
17621767
public static class SubTransactionalAndOrderedClass extends TransactionalAndOrderedClass {
17631768
}
17641769

1765-
public interface InterfaceWithGenericAnnotatedMethod<T> {
1766-
1767-
@Order
1768-
void foo(T t);
1769-
}
1770-
1771-
public static class ImplementsInterfaceWithGenericAnnotatedMethod implements InterfaceWithGenericAnnotatedMethod<String> {
1772-
1773-
public void foo(String t) {
1774-
}
1775-
}
1776-
17771770
public interface InterfaceWithAnnotatedMethod {
17781771

17791772
@Order
@@ -1806,6 +1799,30 @@ public void foo() {
18061799
}
18071800
}
18081801

1802+
public interface InterfaceWithGenericAnnotatedMethod<T> {
1803+
1804+
@Order
1805+
void foo(T t);
1806+
}
1807+
1808+
public static class ImplementsInterfaceWithGenericAnnotatedMethod implements InterfaceWithGenericAnnotatedMethod<String> {
1809+
1810+
public void foo(String t) {
1811+
}
1812+
}
1813+
1814+
public static abstract class BaseClassWithGenericAnnotatedMethod<T> {
1815+
1816+
@Order
1817+
abstract void foo(T t);
1818+
}
1819+
1820+
public static class ExtendsBaseClassWithGenericAnnotatedMethod extends BaseClassWithGenericAnnotatedMethod<String> {
1821+
1822+
public void foo(String t) {
1823+
}
1824+
}
1825+
18091826
@Retention(RetentionPolicy.RUNTIME)
18101827
@Inherited
18111828
@interface MyRepeatableContainer {

0 commit comments

Comments
 (0)