Skip to content

Commit 23d4862

Browse files
committed
Find annotations on implemented generic interface methods as well
Issue: SPR-16060
1 parent c77dbbb commit 23d4862

File tree

2 files changed

+43
-7
lines changed

2 files changed

+43
-7
lines changed

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.apache.commons.logging.LogFactory;
4040

4141
import org.springframework.core.BridgeMethodResolver;
42+
import org.springframework.core.ResolvableType;
4243
import org.springframework.lang.Nullable;
4344
import org.springframework.util.Assert;
4445
import org.springframework.util.ClassUtils;
@@ -588,8 +589,7 @@ private static <A extends Annotation> A searchOnInterfaces(Method method, Class<
588589
Set<Method> annotatedMethods = getAnnotatedMethodsInBaseType(ifc);
589590
if (!annotatedMethods.isEmpty()) {
590591
for (Method annotatedMethod : annotatedMethods) {
591-
if (annotatedMethod.getName().equals(method.getName()) &&
592-
Arrays.equals(annotatedMethod.getParameterTypes(), method.getParameterTypes())) {
592+
if (isOverride(method, annotatedMethod)) {
593593
A annotation = getAnnotation(annotatedMethod, annotationType);
594594
if (annotation != null) {
595595
return annotation;
@@ -647,6 +647,23 @@ private static boolean hasSearchableAnnotations(Method ifcMethod) {
647647
return true;
648648
}
649649

650+
private static boolean isOverride(Method method, Method candidate) {
651+
if (!candidate.getName().equals(method.getName()) ||
652+
candidate.getParameterCount() != method.getParameterCount()) {
653+
return false;
654+
}
655+
Class<?>[] paramTypes = method.getParameterTypes();
656+
if (Arrays.equals(candidate.getParameterTypes(), paramTypes)) {
657+
return true;
658+
}
659+
for (int i = 0; i < paramTypes.length; i++) {
660+
if (paramTypes[i] != ResolvableType.forMethodParameter(candidate, i, method.getDeclaringClass()).resolve()) {
661+
return false;
662+
}
663+
}
664+
return true;
665+
}
666+
650667
/**
651668
* Find a single {@link Annotation} of {@code annotationType} on the
652669
* supplied {@link Class}, traversing its interfaces, annotations, and

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

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,13 @@ public void findMethodAnnotationFromInterface() throws Exception {
178178
assertNotNull(order);
179179
}
180180

181+
@Test // SPR-16060
182+
public void findMethodAnnotationFromGenericInterface() throws Exception {
183+
Method method = ImplementsInterfaceWithGenericAnnotatedMethod.class.getMethod("foo", String.class);
184+
Order order = findAnnotation(method, Order.class);
185+
assertNotNull(order);
186+
}
187+
181188
@Test
182189
public void findMethodAnnotationFromInterfaceOnSuper() throws Exception {
183190
Method method = SubOfImplementsInterfaceWithAnnotatedMethod.class.getMethod("foo");
@@ -286,7 +293,7 @@ public void findClassAnnotationOnSubSubNonInheritedAnnotationInterface() {
286293
}
287294

288295
@Test
289-
public void findAnnotationDeclaringClassForAllScenarios() throws Exception {
296+
public void findAnnotationDeclaringClassForAllScenarios() {
290297
// no class-level annotation
291298
assertNull(findAnnotationDeclaringClass(Transactional.class, NonAnnotatedInterface.class));
292299
assertNull(findAnnotationDeclaringClass(Transactional.class, NonAnnotatedClass.class));
@@ -395,7 +402,7 @@ public void isAnnotationDeclaredLocallyForAllScenarios() throws Exception {
395402
}
396403

397404
@Test
398-
public void isAnnotationInheritedForAllScenarios() throws Exception {
405+
public void isAnnotationInheritedForAllScenarios() {
399406
// no class-level annotation
400407
assertFalse(isAnnotationInherited(Transactional.class, NonAnnotatedInterface.class));
401408
assertFalse(isAnnotationInherited(Transactional.class, NonAnnotatedClass.class));
@@ -504,7 +511,7 @@ public void getDefaultValueFromAnnotation() throws Exception {
504511
}
505512

506513
@Test
507-
public void getDefaultValueFromNonPublicAnnotation() throws Exception {
514+
public void getDefaultValueFromNonPublicAnnotation() {
508515
Annotation[] declaredAnnotations = NonPublicAnnotatedClass.class.getDeclaredAnnotations();
509516
assertEquals(1, declaredAnnotations.length);
510517
Annotation annotation = declaredAnnotations[0];
@@ -515,7 +522,7 @@ public void getDefaultValueFromNonPublicAnnotation() throws Exception {
515522
}
516523

517524
@Test
518-
public void getDefaultValueFromAnnotationType() throws Exception {
525+
public void getDefaultValueFromAnnotationType() {
519526
assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(Order.class, VALUE));
520527
assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(Order.class));
521528
}
@@ -547,7 +554,7 @@ public void getRepeatableAnnotationsDeclaredOnClassWithMissingAttributeAliasDecl
547554
}
548555

549556
@Test
550-
public void getRepeatableAnnotationsDeclaredOnClassWithAttributeAliases() throws Exception {
557+
public void getRepeatableAnnotationsDeclaredOnClassWithAttributeAliases() {
551558
final List<String> expectedLocations = asList("A", "B");
552559

553560
Set<ContextConfig> annotations = getRepeatableAnnotations(ConfigHierarchyTestCase.class, ContextConfig.class, null);
@@ -1750,6 +1757,18 @@ public static class TransactionalAndOrderedClass extends TransactionalClass {
17501757
public static class SubTransactionalAndOrderedClass extends TransactionalAndOrderedClass {
17511758
}
17521759

1760+
public interface InterfaceWithGenericAnnotatedMethod<T> {
1761+
1762+
@Order
1763+
void foo(T t);
1764+
}
1765+
1766+
public static class ImplementsInterfaceWithGenericAnnotatedMethod implements InterfaceWithGenericAnnotatedMethod<String> {
1767+
1768+
public void foo(String t) {
1769+
}
1770+
}
1771+
17531772
public interface InterfaceWithAnnotatedMethod {
17541773

17551774
@Order

0 commit comments

Comments
 (0)