From 8576962cf8a57647f014ed5388dabad8610d66c5 Mon Sep 17 00:00:00 2001 From: cuitianhao <54015884+tianhaocui@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:30:37 +0800 Subject: [PATCH] Resolve WildcardType in GenericTypeResolver.resolveType() GenericTypeResolver.resolveType() did not handle WildcardType, causing wildcard type arguments like '? extends T' to remain unresolved when the type variable T could be resolved from the context class. This adds WildcardType handling in two places: - Top-level: resolves the upper bound of a wildcard recursively - Inside ParameterizedType argument loop: delegates wildcard arguments to resolveType() for recursive resolution Closes spring-projects#36474 Signed-off-by: tianhaocui <54015884+tianhaocui@users.noreply.github.com> Signed-off-by: cuitianhao <54015884+tianhaocui@users.noreply.github.com> --- .../core/GenericTypeResolver.java | 9 ++++++++ .../core/GenericTypeResolverTests.java | 23 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java index 083964732733..c4b83c3707ac 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -193,6 +193,9 @@ else if (genericType instanceof ParameterizedType parameterizedType) { else if (typeArgument instanceof ParameterizedType) { generics[i] = ResolvableType.forType(resolveType(typeArgument, contextClass)); } + else if (typeArgument instanceof WildcardType) { + generics[i] = ResolvableType.forType(resolveType(typeArgument, contextClass)); + } else { generics[i] = ResolvableType.forType(typeArgument); } @@ -203,6 +206,12 @@ else if (typeArgument instanceof ParameterizedType) { } } } + else if (genericType instanceof WildcardType wildcardType) { + Type[] upperBounds = wildcardType.getUpperBounds(); + if (upperBounds.length == 1) { + return resolveType(upperBounds[0], contextClass); + } + } } return genericType; } diff --git a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java index 7e4e3542c021..eba184bf57c9 100644 --- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java @@ -260,6 +260,16 @@ void resolveTypeFromNestedParameterizedType() { assertThat(resolvedType).isEqualTo(method(MyOptionalInterfaceType.class, "get").getGenericReturnType()); } + @Test + void resolveWildcardTypeArgument() { + Type genericReturnType = method(WildcardService.class, "findAll").getGenericReturnType(); + Type resolvedType = resolveType(genericReturnType, PersonWildcardService.class); + assertThat(resolvedType).isInstanceOf(ParameterizedType.class); + ParameterizedType parameterizedType = (ParameterizedType) resolvedType; + assertThat(parameterizedType.getRawType()).isEqualTo(List.class); + assertThat(parameterizedType.getActualTypeArguments()[0]).isEqualTo(String.class); + } + private static Method method(Class target, String methodName, Class... parameterTypes) { Method method = findMethod(target, methodName, parameterTypes); assertThat(method).describedAs(target.getName() + "#" + methodName).isNotNull(); @@ -504,4 +514,17 @@ static class ConcreteType implements InterfaceWithDefaultMethod.AbstractType { } } + interface WildcardService { + + List findAll(); + } + + static class PersonWildcardService implements WildcardService { + + @Override + public List findAll() { + return List.of(); + } + } + }