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 27f8682f474b..b3bd606aa4e0 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -39,6 +39,7 @@ * @author Rob Harrop * @author Sam Brannen * @author Phillip Webb + * @author Sebastien Deleuze * @since 2.5.2 */ public final class GenericTypeResolver { @@ -166,29 +167,30 @@ public static Type resolveType(Type genericType, @Nullable Class> contextClass } else if (genericType instanceof ParameterizedType parameterizedType) { ResolvableType resolvedType = ResolvableType.forType(genericType); - if (resolvedType.hasUnresolvableGenerics()) { - Class>[] generics = new Class>[parameterizedType.getActualTypeArguments().length]; - Type[] typeArguments = parameterizedType.getActualTypeArguments(); - ResolvableType contextType = ResolvableType.forClass(contextClass); - for (int i = 0; i < typeArguments.length; i++) { - Type typeArgument = typeArguments[i]; - if (typeArgument instanceof TypeVariable> typeVariable) { - ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType); - if (resolvedTypeArgument != ResolvableType.NONE) { - generics[i] = resolvedTypeArgument.resolve(); - } - else { - generics[i] = ResolvableType.forType(typeArgument).resolve(); - } + Class>[] generics = new Class>[parameterizedType.getActualTypeArguments().length]; + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + ResolvableType contextType = ResolvableType.forClass(contextClass); + for (int i = 0; i < typeArguments.length; i++) { + Type typeArgument = typeArguments[i]; + if (typeArgument instanceof TypeVariable> typeVariable) { + ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType); + if (resolvedTypeArgument != ResolvableType.NONE) { + generics[i] = resolvedTypeArgument.resolve(); } else { generics[i] = ResolvableType.forType(typeArgument).resolve(); } } - Class> rawClass = resolvedType.getRawClass(); - if (rawClass != null) { - return ResolvableType.forClassWithGenerics(rawClass, generics).getType(); + else if (typeArgument instanceof WildcardType wildcardType) { + generics[i] = resolveWildcard(wildcardType, contextType).resolve(); } + else { + generics[i] = ResolvableType.forType(typeArgument).resolve(); + } + } + Class> rawClass = resolvedType.getRawClass(); + if (rawClass != null) { + return ResolvableType.forClassWithGenerics(rawClass, generics).getType(); } } } @@ -224,6 +226,26 @@ private static ResolvableType resolveVariable(TypeVariable> typeVariable, Reso return ResolvableType.NONE; } + private static ResolvableType resolveWildcard(WildcardType wildcardType, ResolvableType contextType) { + for (Type bound : wildcardType.getUpperBounds()) { + if (bound instanceof TypeVariable> typeVariable) { + ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType); + if (resolvedTypeArgument != ResolvableType.NONE) { + return resolvedTypeArgument; + } + } + } + for (Type bound : wildcardType.getLowerBounds()) { + if (bound instanceof TypeVariable> typeVariable) { + ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType); + if (resolvedTypeArgument != ResolvableType.NONE) { + return resolvedTypeArgument; + } + } + } + return ResolvableType.forType(wildcardType); + } + /** * Resolve the specified generic type against the given TypeVariable map. *
Used by Spring Data.
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 46e19190f4f5..5995c9f669a5 100644
--- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java
+++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@
/**
* @author Juergen Hoeller
* @author Sam Brannen
+ * @author Sebastien Deleuze
*/
@SuppressWarnings({"unchecked", "rawtypes"})
class GenericTypeResolverTests {
@@ -185,6 +186,51 @@ public void resolveTransitiveTypeVariableWithDifferentName() {
assertThat(resolved).isEqualTo(E.class);
}
+ @Test
+ void resolveWildcardTypeWithUpperBound() {
+ Method method = findMethod(MySimpleSuperclassType.class, "upperBound", List.class);
+ Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class);
+ ResolvableType resolvableType = ResolvableType.forType(resolved);
+ assertThat(resolvableType.hasUnresolvableGenerics()).isFalse();
+ assertThat(resolvableType.resolveGenerics()).containsExactly(String.class);
+ }
+
+ @Test
+ void resolveWildcardTypeWithUpperBoundWithResolvedType() {
+ Method method = findMethod(MySimpleSuperclassType.class, "upperBoundWithResolvedType", List.class);
+ Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class);
+ ResolvableType resolvableType = ResolvableType.forType(resolved);
+ assertThat(resolvableType.hasUnresolvableGenerics()).isFalse();
+ assertThat(resolvableType.resolveGenerics()).containsExactly(Integer.class);
+ }
+
+ @Test
+ void resolveWildcardTypeWithLowerBound() {
+ Method method = findMethod(MySimpleSuperclassType.class, "lowerBound", List.class);
+ Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class);
+ ResolvableType resolvableType = ResolvableType.forType(resolved);
+ assertThat(resolvableType.hasUnresolvableGenerics()).isFalse();
+ assertThat(resolvableType.resolveGenerics()).containsExactly(String.class);
+ }
+
+ @Test
+ void resolveWildcardTypeWithLowerBoundWithResolvedType() {
+ Method method = findMethod(MySimpleSuperclassType.class, "lowerBoundWithResolvedType", List.class);
+ Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class);
+ ResolvableType resolvableType = ResolvableType.forType(resolved);
+ assertThat(resolvableType.hasUnresolvableGenerics()).isFalse();
+ assertThat(resolvableType.resolveGenerics()).containsExactly(Integer.class);
+ }
+
+ @Test
+ void resolveWildcardTypeWithUnbounded() {
+ Method method = findMethod(MySimpleSuperclassType.class, "unbounded", List.class);
+ Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class);
+ ResolvableType resolvableType = ResolvableType.forType(resolved);
+ assertThat(resolvableType.hasUnresolvableGenerics()).isFalse();
+ assertThat(resolvableType.resolveGenerics()).containsExactly(Object.class);
+ }
+
public interface MyInterfaceType