Skip to content

Commit 688ef9a

Browse files
committed
Find annotations on implemented generic superclass methods as well
Includes Java 8 getDeclaredAnnotation shortcut for lookup on Class. Issue: SPR-17146 (cherry picked from commit 4521a79)
1 parent 9580bbd commit 688ef9a

File tree

4 files changed

+99
-80
lines changed

4 files changed

+99
-80
lines changed

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

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -386,13 +386,9 @@ 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);
396392
}
397393

398394
// Exhaustive retrieval of merged annotation attributes...
@@ -671,13 +667,9 @@ public static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedEleme
671667
@Nullable
672668
public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
673669
// 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-
}
670+
A annotation = element.getDeclaredAnnotation(annotationType);
671+
if (annotation != null) {
672+
return AnnotationUtils.synthesizeAnnotation(annotation, element);
681673
}
682674

683675
// Exhaustive retrieval of merged annotation attributes...
@@ -1140,8 +1132,7 @@ else if (currentAnnotationType == containerType) {
11401132
Set<Method> annotatedMethods = AnnotationUtils.getAnnotatedMethodsInBaseType(clazz);
11411133
if (!annotatedMethods.isEmpty()) {
11421134
for (Method annotatedMethod : annotatedMethods) {
1143-
if (annotatedMethod.getName().equals(method.getName()) &&
1144-
Arrays.equals(annotatedMethod.getParameterTypes(), method.getParameterTypes())) {
1135+
if (AnnotationUtils.isOverride(method, annotatedMethod)) {
11451136
Method resolvedSuperMethod = BridgeMethodResolver.findBridgedMethod(annotatedMethod);
11461137
result = searchWithFindSemantics(resolvedSuperMethod, annotationType, annotationName,
11471138
containerType, processor, visited, metaDepth);
@@ -1198,8 +1189,7 @@ private static <T> T searchOnInterfaces(Method method, @Nullable Class<? extends
11981189
Set<Method> annotatedMethods = AnnotationUtils.getAnnotatedMethodsInBaseType(ifc);
11991190
if (!annotatedMethods.isEmpty()) {
12001191
for (Method annotatedMethod : annotatedMethods) {
1201-
if (annotatedMethod.getName().equals(method.getName()) &&
1202-
Arrays.equals(annotatedMethod.getParameterTypes(), method.getParameterTypes())) {
1192+
if (AnnotationUtils.isOverride(method, annotatedMethod)) {
12031193
T result = searchWithFindSemantics(annotatedMethod, annotationType, annotationName,
12041194
containerType, processor, visited, metaDepth);
12051195
if (result != null) {
@@ -1275,7 +1265,7 @@ private static Class<? extends Annotation> resolveContainerType(Class<? extends
12751265

12761266
/**
12771267
* Validate that the supplied {@code containerType} is a proper container
1278-
* annotation for the supplied repeatable {@code annotationType} (i.e.,
1268+
* annotation for the supplied repeatable {@code annotationType} (i.e.
12791269
* that it declares a {@code value} attribute that holds an array of the
12801270
* {@code annotationType}).
12811271
* @throws AnnotationConfigurationException if the supplied {@code containerType}
@@ -1317,27 +1307,24 @@ private static <A extends Annotation> Set<A> postProcessAndSynthesizeAggregatedR
13171307

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

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

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

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

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

843842
/**
844843
* Determine whether an annotation of the specified {@code annotationType}
845-
* is declared locally (i.e., <em>directly present</em>) on the supplied
844+
* is declared locally (i.e. <em>directly present</em>) on the supplied
846845
* {@code clazz}.
847846
* <p>The supplied {@link Class} may represent any type.
848847
* <p>Meta-annotations will <em>not</em> be searched.
@@ -871,8 +870,8 @@ public static boolean isAnnotationDeclaredLocally(Class<? extends Annotation> an
871870
/**
872871
* Determine whether an annotation of the specified {@code annotationType}
873872
* is <em>present</em> on the supplied {@code clazz} and is
874-
* {@linkplain java.lang.annotation.Inherited inherited} (i.e., not
875-
* <em>directly present</em>).
873+
* {@linkplain java.lang.annotation.Inherited inherited}
874+
* (i.e. not <em>directly present</em>).
876875
* <p>Meta-annotations will <em>not</em> be searched.
877876
* <p>If the supplied {@code clazz} is an interface, only the interface
878877
* itself will be checked. In accordance with standard meta-annotation
@@ -1652,10 +1651,10 @@ static <A extends Annotation> A[] synthesizeAnnotationArray(
16521651
* in the supplied annotation type.
16531652
* <p>The map is keyed by attribute name with each value representing
16541653
* a list of names of aliased attributes.
1655-
* <p>For <em>explicit</em> alias pairs such as x and y (i.e., where x
1654+
* <p>For <em>explicit</em> alias pairs such as x and y (i.e. where x
16561655
* is an {@code @AliasFor("y")} and y is an {@code @AliasFor("x")}, there
16571656
* will be two entries in the map: {@code x -> (y)} and {@code y -> (x)}.
1658-
* <p>For <em>implicit</em> aliases (i.e., attributes that are declared
1657+
* <p>For <em>implicit</em> aliases (i.e. attributes that are declared
16591658
* as attribute overrides for the same attribute in the same meta-annotation),
16601659
* there will be n entries in the map. For example, if x, y, and z are
16611660
* implicit aliases, the map will contain the following entries:
@@ -1704,7 +1703,7 @@ private static boolean canExposeSynthesizedMarker(Class<? extends Annotation> an
17041703

17051704
/**
17061705
* Determine if annotations of the supplied {@code annotationType} are
1707-
* <em>synthesizable</em> (i.e., in need of being wrapped in a dynamic
1706+
* <em>synthesizable</em> (i.e. in need of being wrapped in a dynamic
17081707
* proxy that provides functionality above that of a standard JDK
17091708
* annotation).
17101709
* <p>Specifically, an annotation is <em>synthesizable</em> if it declares
@@ -1769,7 +1768,7 @@ else if (Annotation.class.isAssignableFrom(returnType)) {
17691768
*/
17701769
static List<String> getAttributeAliasNames(Method attribute) {
17711770
AliasDescriptor descriptor = AliasDescriptor.from(attribute);
1772-
return (descriptor != null ? descriptor.getAttributeAliasNames() : Collections.<String> emptyList());
1771+
return (descriptor != null ? descriptor.getAttributeAliasNames() : Collections.emptyList());
17731772
}
17741773

17751774
/**

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: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.lang.annotation.RetentionPolicy;
2424
import java.lang.annotation.Target;
2525
import java.lang.reflect.Method;
26+
import java.util.Arrays;
2627
import java.util.Collections;
2728
import java.util.List;
2829
import java.util.Map;
@@ -145,7 +146,16 @@ public void findMethodAnnotationOnBridgeMethod() throws Exception {
145146
assertNull(getAnnotation(bridgeMethod, Order.class));
146147
assertNotNull(findAnnotation(bridgeMethod, Order.class));
147148

148-
assertNotNull(bridgeMethod.getAnnotation(Transactional.class));
149+
boolean runningInEclipse = Arrays.stream(new Exception().getStackTrace())
150+
.anyMatch(element -> element.getClassName().startsWith("org.eclipse.jdt"));
151+
152+
// As of JDK 8, invoking getAnnotation() on a bridge method actually finds an
153+
// annotation on its 'bridged' method; however, the Eclipse compiler still does
154+
// not properly support this. Thus, we effectively ignore the following assertion
155+
// if the test is currently executing within the Eclipse IDE.
156+
if (!runningInEclipse) {
157+
assertNotNull(bridgeMethod.getAnnotation(Transactional.class));
158+
}
149159
assertNotNull(getAnnotation(bridgeMethod, Transactional.class));
150160
assertNotNull(findAnnotation(bridgeMethod, Transactional.class));
151161
}
@@ -157,9 +167,7 @@ public void findMethodAnnotationOnBridgedMethod() throws Exception {
157167

158168
assertNull(bridgedMethod.getAnnotation(Order.class));
159169
assertNull(getAnnotation(bridgedMethod, Order.class));
160-
// AnnotationUtils.findAnnotation(Method, Class<A>) will not find an annotation on
161-
// the bridge method for a bridged method.
162-
assertNull(findAnnotation(bridgedMethod, Order.class));
170+
assertNotNull(findAnnotation(bridgedMethod, Order.class));
163171

164172
assertNotNull(bridgedMethod.getAnnotation(Transactional.class));
165173
assertNotNull(getAnnotation(bridgedMethod, Transactional.class));
@@ -180,6 +188,13 @@ public void findMethodAnnotationFromGenericInterface() throws Exception {
180188
assertNotNull(order);
181189
}
182190

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+
183198
@Test
184199
public void findMethodAnnotationFromInterfaceOnSuper() throws Exception {
185200
Method method = SubOfImplementsInterfaceWithAnnotatedMethod.class.getMethod("foo");
@@ -194,23 +209,23 @@ public void findMethodAnnotationFromInterfaceWhenSuperDoesNotImplementMethod() t
194209
assertNotNull(order);
195210
}
196211

197-
/** @since 4.1.2 */
212+
// @since 4.1.2
198213
@Test
199214
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverAnnotationsOnInterfaces() {
200215
Component component = findAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, Component.class);
201216
assertNotNull(component);
202217
assertEquals("meta2", component.value());
203218
}
204219

205-
/** @since 4.0.3 */
220+
// @since 4.0.3
206221
@Test
207222
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverInheritedAnnotations() {
208223
Transactional transactional = findAnnotation(SubSubClassWithInheritedAnnotation.class, Transactional.class);
209224
assertNotNull(transactional);
210225
assertTrue("readOnly flag for SubSubClassWithInheritedAnnotation", transactional.readOnly());
211226
}
212227

213-
/** @since 4.0.3 */
228+
// @since 4.0.3
214229
@Test
215230
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverInheritedComposedAnnotations() {
216231
Component component = findAnnotation(SubSubClassWithInheritedMetaAnnotation.class, Component.class);
@@ -1752,18 +1767,6 @@ public static class TransactionalAndOrderedClass extends TransactionalClass {
17521767
public static class SubTransactionalAndOrderedClass extends TransactionalAndOrderedClass {
17531768
}
17541769

1755-
public interface InterfaceWithGenericAnnotatedMethod<T> {
1756-
1757-
@Order
1758-
void foo(T t);
1759-
}
1760-
1761-
public static class ImplementsInterfaceWithGenericAnnotatedMethod implements InterfaceWithGenericAnnotatedMethod<String> {
1762-
1763-
public void foo(String t) {
1764-
}
1765-
}
1766-
17671770
public interface InterfaceWithAnnotatedMethod {
17681771

17691772
@Order
@@ -1796,6 +1799,30 @@ public void foo() {
17961799
}
17971800
}
17981801

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+
17991826
@Retention(RetentionPolicy.RUNTIME)
18001827
@Inherited
18011828
@interface MyRepeatableContainer {

0 commit comments

Comments
 (0)