diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index 7e534efb38..1269385449 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -39,6 +39,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.BridgeMethodResolver; +import org.springframework.core.ResolvableType; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -588,8 +589,7 @@ public abstract class AnnotationUtils { Set annotatedMethods = getAnnotatedMethodsInBaseType(ifc); if (!annotatedMethods.isEmpty()) { for (Method annotatedMethod : annotatedMethods) { - if (annotatedMethod.getName().equals(method.getName()) && - Arrays.equals(annotatedMethod.getParameterTypes(), method.getParameterTypes())) { + if (isOverride(method, annotatedMethod)) { A annotation = getAnnotation(annotatedMethod, annotationType); if (annotation != null) { return annotation; @@ -647,6 +647,23 @@ public abstract class AnnotationUtils { return true; } + private static boolean isOverride(Method method, Method candidate) { + if (!candidate.getName().equals(method.getName()) || + candidate.getParameterCount() != method.getParameterCount()) { + return false; + } + Class[] paramTypes = method.getParameterTypes(); + if (Arrays.equals(candidate.getParameterTypes(), paramTypes)) { + return true; + } + for (int i = 0; i < paramTypes.length; i++) { + if (paramTypes[i] != ResolvableType.forMethodParameter(candidate, i, method.getDeclaringClass()).resolve()) { + return false; + } + } + return true; + } + /** * Find a single {@link Annotation} of {@code annotationType} on the * supplied {@link Class}, traversing its interfaces, annotations, and @@ -1833,8 +1850,8 @@ public abstract class AnnotationUtils { /** * Determine if the supplied method is an "annotationType" method. * @return {@code true} if the method is an "annotationType" method - * @see Annotation#annotationType() * @since 4.2 + * @see Annotation#annotationType() */ static boolean isAnnotationTypeMethod(@Nullable Method method) { return (method != null && method.getName().equals("annotationType") && method.getParameterCount() == 0); @@ -2047,7 +2064,7 @@ public abstract class AnnotationUtils { * @see #getAttributeAliasNames * @see #getAttributeOverrideName */ - private static class AliasDescriptor { + private static final class AliasDescriptor { private final Method sourceAttribute; 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 76ea099043..a62131a8dd 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 @@ -178,6 +178,13 @@ public class AnnotationUtilsTests { assertNotNull(order); } + @Test // SPR-16060 + public void findMethodAnnotationFromGenericInterface() throws Exception { + Method method = ImplementsInterfaceWithGenericAnnotatedMethod.class.getMethod("foo", String.class); + Order order = findAnnotation(method, Order.class); + assertNotNull(order); + } + @Test public void findMethodAnnotationFromInterfaceOnSuper() throws Exception { Method method = SubOfImplementsInterfaceWithAnnotatedMethod.class.getMethod("foo"); @@ -286,7 +293,7 @@ public class AnnotationUtilsTests { } @Test - public void findAnnotationDeclaringClassForAllScenarios() throws Exception { + public void findAnnotationDeclaringClassForAllScenarios() { // no class-level annotation assertNull(findAnnotationDeclaringClass(Transactional.class, NonAnnotatedInterface.class)); assertNull(findAnnotationDeclaringClass(Transactional.class, NonAnnotatedClass.class)); @@ -395,7 +402,7 @@ public class AnnotationUtilsTests { } @Test - public void isAnnotationInheritedForAllScenarios() throws Exception { + public void isAnnotationInheritedForAllScenarios() { // no class-level annotation assertFalse(isAnnotationInherited(Transactional.class, NonAnnotatedInterface.class)); assertFalse(isAnnotationInherited(Transactional.class, NonAnnotatedClass.class)); @@ -504,7 +511,7 @@ public class AnnotationUtilsTests { } @Test - public void getDefaultValueFromNonPublicAnnotation() throws Exception { + public void getDefaultValueFromNonPublicAnnotation() { Annotation[] declaredAnnotations = NonPublicAnnotatedClass.class.getDeclaredAnnotations(); assertEquals(1, declaredAnnotations.length); Annotation annotation = declaredAnnotations[0]; @@ -515,7 +522,7 @@ public class AnnotationUtilsTests { } @Test - public void getDefaultValueFromAnnotationType() throws Exception { + public void getDefaultValueFromAnnotationType() { assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(Order.class, VALUE)); assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(Order.class)); } @@ -547,7 +554,7 @@ public class AnnotationUtilsTests { } @Test - public void getRepeatableAnnotationsDeclaredOnClassWithAttributeAliases() throws Exception { + public void getRepeatableAnnotationsDeclaredOnClassWithAttributeAliases() { final List expectedLocations = asList("A", "B"); Set annotations = getRepeatableAnnotations(ConfigHierarchyTestCase.class, ContextConfig.class, null); @@ -1750,6 +1757,18 @@ public class AnnotationUtilsTests { public static class SubTransactionalAndOrderedClass extends TransactionalAndOrderedClass { } + public interface InterfaceWithGenericAnnotatedMethod { + + @Order + void foo(T t); + } + + public static class ImplementsInterfaceWithGenericAnnotatedMethod implements InterfaceWithGenericAnnotatedMethod { + + public void foo(String t) { + } + } + public interface InterfaceWithAnnotatedMethod { @Order