diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java index f4c960b923a1..09d5aab512a9 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java @@ -342,10 +342,50 @@ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { } public boolean matchesFallback(TypeDescriptor sourceType, TypeDescriptor targetType) { - return (this.typeInfo.getTargetType() == targetType.getObjectType() && - this.targetType.hasUnresolvableGenerics() && - (!(this.converter instanceof ConditionalConverter conditionalConverter) || - conditionalConverter.matches(sourceType, targetType))); + if (this.typeInfo.getTargetType() != targetType.getObjectType()) { + return false; + } + + if (!this.targetType.hasUnresolvableGenerics()) { + return false; + } + + boolean assignable = isAssignableTo(targetType.getResolvableType()); + if (!assignable) { + return false; + } + + return !(this.converter instanceof ConditionalConverter conditionalConverter) || + conditionalConverter.matches(sourceType, targetType); + } + + private boolean isAssignableTo(ResolvableType expected) { + return isAssignable(this.targetType, expected); + } + + private boolean isAssignable(ResolvableType actual, ResolvableType expected) { + Class actualResolved = actual.resolve(Object.class); + Class expectedResolved = expected.resolve(Object.class); + if (!expectedResolved.isAssignableFrom(actualResolved)) { + return false; + } + + ResolvableType[] actualGenerics = actual.getGenerics(); + ResolvableType[] expectedGenerics = expected.getGenerics(); + + if (actualGenerics.length != expectedGenerics.length) { + return false; + } + + for (int i = 0; i < actualGenerics.length; i++) { + ResolvableType actualGeneric = actualGenerics[i]; + ResolvableType expectedGeneric = expectedGenerics[i]; + if (!isAssignable(actualGeneric, expectedGeneric)) { + return false; + } + } + + return true; } @Override diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java index 77a72350808e..9b151320f8a4 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java @@ -594,6 +594,19 @@ void stringToListOfMapConverterWithFallbackMatch() { assertThat("foo").isEqualTo(result.get(0).get("bar")); } + @Test + @SuppressWarnings("unchecked") + void stringToListOfString() { + conversionService.addConverter(new StringToCollectionConverter(conversionService)); + conversionService.addConverter(new StringToListOfMapConverter()); + + List result = (List) conversionService.convert("foo,bar", + TypeDescriptor.valueOf(String.class), + TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)) + ); + + assertThat(result.get(0)).isEqualTo("foo"); + } @ExampleAnnotation(active = true) public String annotatedString; @@ -990,5 +1003,4 @@ private static class StringToListOfMapConverter implements Converter