Skip to content

Commit da6753b

Browse files
sbrannenErik Pearson
authored andcommitted
Find annotation on overridden method in type hierarchy with unresolved generics
Prior to this commit, the MergedAnnotations support (specifically AnnotationsScanner) and AnnotatedMethod did not find annotations on overridden methods in type hierarchies with unresolved generics. The reason for this is that ResolvableType.resolve() returns null for such an unresolved type, which prevents the search algorithms from considering such methods as override candidates. For example, given the following type hierarchy, the compiler does not generate a method corresponding to processOneAndTwo(Long, String) for GenericInterfaceImpl. Nonetheless, one would expect an invocation of processOneAndTwo(Long, String) to be @⁠Transactional since it is effectively an invocation of processOneAndTwo(Long, C) in GenericAbstractSuperclass, which overrides/implements processOneAndTwo(A, B) in GenericInterface, which is annotated with @⁠Transactional. However, the MergedAnnotations infrastructure currently does not determine that processOneAndTwo(Long, C) is @⁠Transactional since it is not able to determine that processOneAndTwo(Long, C) overrides processOneAndTwo(A, B) because of the unresolved generic C. interface GenericInterface<A, B> { @⁠Transactional void processOneAndTwo(A value1, B value2); } abstract class GenericAbstractSuperclass<C> implements GenericInterface<Long, C> { @⁠Override public void processOneAndTwo(Long value1, C value2) { } } static GenericInterfaceImpl extends GenericAbstractSuperclass<String> { } To address such issues, this commit changes the logic in AnnotationsScanner.hasSameGenericTypeParameters() and AnnotatedMethod.isOverrideFor() so that they use ResolvableType.toClass() instead of ResolvableType.resolve(). The former returns Object.class for an unresolved generic which in turn allows the search algorithms to properly detect method overrides in such type hierarchies. Closes spring-projectsgh-35342
1 parent b64048a commit da6753b

File tree

3 files changed

+31
-2
lines changed

3 files changed

+31
-2
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ private static boolean hasSameGenericTypeParameters(
383383
}
384384
for (int i = 0; i < rootParameterTypes.length; i++) {
385385
Class<?> resolvedParameterType = ResolvableType.forMethodParameter(
386-
candidateMethod, i, sourceDeclaringClass).resolve();
386+
candidateMethod, i, sourceDeclaringClass).toClass();
387387
if (rootParameterTypes[i] != resolvedParameterType) {
388388
return false;
389389
}

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,15 @@ void getFromMethodWithGenericSuperclass() throws Exception {
945945
Order.class).getDistance()).isEqualTo(0);
946946
}
947947

948+
@Test
949+
void getFromMethodWithUnresolvedGenericsInGenericTypeHierarchy() {
950+
// The following method is GenericAbstractSuperclass.processOneAndTwo(java.lang.Long, C),
951+
// where 'C' is an unresolved generic, for which ResolvableType.resolve() returns null.
952+
Method method = ClassUtils.getMethod(GenericInterfaceImpl.class, "processOneAndTwo", Long.class, Object.class);
953+
assertThat(MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY)
954+
.get(Transactional.class).isDirectlyPresent()).isTrue();
955+
}
956+
948957
@Test
949958
void getFromMethodWithInterfaceOnSuper() throws Exception {
950959
Method method = SubOfImplementsInterfaceWithAnnotatedMethod.class.getMethod("foo");
@@ -3032,6 +3041,26 @@ public void foo(String t) {
30323041
}
30333042
}
30343043

3044+
interface GenericInterface<A, B> {
3045+
3046+
@Transactional
3047+
void processOneAndTwo(A value1, B value2);
3048+
}
3049+
3050+
abstract static class GenericAbstractSuperclass<C> implements GenericInterface<Long, C> {
3051+
3052+
@Override
3053+
public void processOneAndTwo(Long value1, C value2) {
3054+
}
3055+
}
3056+
3057+
static class GenericInterfaceImpl extends GenericAbstractSuperclass<String> {
3058+
// The compiler does not require us to declare a concrete
3059+
// processOneAndTwo(Long, String) method, and we intentionally
3060+
// do not declare one here.
3061+
}
3062+
3063+
30353064
@Retention(RetentionPolicy.RUNTIME)
30363065
@Inherited
30373066
@interface MyRepeatableContainer {

spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ private boolean isOverrideFor(Method candidate) {
415415
}
416416
for (int i = 0; i < paramTypes.length; i++) {
417417
if (paramTypes[i] !=
418-
ResolvableType.forMethodParameter(candidate, i, this.method.getDeclaringClass()).resolve()) {
418+
ResolvableType.forMethodParameter(candidate, i, this.method.getDeclaringClass()).toClass()) {
419419
return false;
420420
}
421421
}

0 commit comments

Comments
 (0)