Skip to content

Commit 8ee92f1

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 0934ed5 commit 8ee92f1

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
@@ -386,7 +386,7 @@ private static boolean hasSameGenericTypeParameters(
386386
}
387387
for (int i = 0; i < rootParameterTypes.length; i++) {
388388
Class<?> resolvedParameterType = ResolvableType.forMethodParameter(
389-
candidateMethod, i, sourceDeclaringClass).resolve();
389+
candidateMethod, i, sourceDeclaringClass).toClass();
390390
if (rootParameterTypes[i] != resolvedParameterType) {
391391
return false;
392392
}

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
@@ -831,6 +831,15 @@ void getFromMethodWithGenericSuperclass() throws Exception {
831831
Order.class).getDistance()).isEqualTo(0);
832832
}
833833

834+
@Test
835+
void getFromMethodWithUnresolvedGenericsInGenericTypeHierarchy() {
836+
// The following method is GenericAbstractSuperclass.processOneAndTwo(java.lang.Long, C),
837+
// where 'C' is an unresolved generic, for which ResolvableType.resolve() returns null.
838+
Method method = ClassUtils.getMethod(GenericInterfaceImpl.class, "processOneAndTwo", Long.class, Object.class);
839+
assertThat(MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY)
840+
.get(Transactional.class).isDirectlyPresent()).isTrue();
841+
}
842+
834843
@Test
835844
void getFromMethodWithInterfaceOnSuper() throws Exception {
836845
Method method = SubOfImplementsInterfaceWithAnnotatedMethod.class.getMethod("foo");
@@ -2882,6 +2891,26 @@ public void foo(String t) {
28822891
}
28832892
}
28842893

2894+
interface GenericInterface<A, B> {
2895+
2896+
@Transactional
2897+
void processOneAndTwo(A value1, B value2);
2898+
}
2899+
2900+
abstract static class GenericAbstractSuperclass<C> implements GenericInterface<Long, C> {
2901+
2902+
@Override
2903+
public void processOneAndTwo(Long value1, C value2) {
2904+
}
2905+
}
2906+
2907+
static class GenericInterfaceImpl extends GenericAbstractSuperclass<String> {
2908+
// The compiler does not require us to declare a concrete
2909+
// processOneAndTwo(Long, String) method, and we intentionally
2910+
// do not declare one here.
2911+
}
2912+
2913+
28852914
@Retention(RetentionPolicy.RUNTIME)
28862915
@Inherited
28872916
@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
@@ -416,7 +416,7 @@ private boolean isOverrideFor(Method candidate) {
416416
}
417417
for (int i = 0; i < paramTypes.length; i++) {
418418
if (paramTypes[i] !=
419-
ResolvableType.forMethodParameter(candidate, i, this.method.getDeclaringClass()).resolve()) {
419+
ResolvableType.forMethodParameter(candidate, i, this.method.getDeclaringClass()).toClass()) {
420420
return false;
421421
}
422422
}

0 commit comments

Comments
 (0)