From 795ce1658c284eb803289bdfa989d8b111fabdd5 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 24 Oct 2023 16:42:52 +0200 Subject: [PATCH] Consistent Class and array matching with Class comparison shortcut Closes gh-31487 --- .../springframework/core/ResolvableType.java | 48 ++++++++++++------- .../core/ResolvableTypeTests.java | 31 +++++++----- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index dd03fa8295..97a346fbac 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -262,7 +262,9 @@ public class ResolvableType implements Serializable { * @see #isAssignableFrom(ResolvableType) */ public boolean isAssignableFrom(Class other) { - return isAssignableFrom(forClass(other), null); + // As of 6.1: shortcut assignability check for top-level Class references + return (this.type instanceof Class clazz ? ClassUtils.isAssignable(clazz, other) : + isAssignableFrom(forClass(other), false, null)); } /** @@ -277,10 +279,10 @@ public class ResolvableType implements Serializable { * {@code ResolvableType}; {@code false} otherwise */ public boolean isAssignableFrom(ResolvableType other) { - return isAssignableFrom(other, null); + return isAssignableFrom(other, false, null); } - private boolean isAssignableFrom(ResolvableType other, @Nullable Map matchedBefore) { + private boolean isAssignableFrom(ResolvableType other, boolean strict, @Nullable Map matchedBefore) { Assert.notNull(other, "ResolvableType must not be null"); // If we cannot resolve types, we are not assignable @@ -288,13 +290,21 @@ public class ResolvableType implements Serializable { return false; } - // Deal with array by delegating to the component type - if (isArray()) { - return (other.isArray() && getComponentType().isAssignableFrom(other.getComponentType())); + if (matchedBefore != null) { + if (matchedBefore.get(this.type) == other.type) { + return true; + } + } + else { + // As of 6.1: shortcut assignability check for top-level Class references + if (this.type instanceof Class clazz && other.type instanceof Class otherClazz) { + return (strict ? clazz.isAssignableFrom(otherClazz) : ClassUtils.isAssignable(clazz, otherClazz)); + } } - if (matchedBefore != null && matchedBefore.get(this.type) == other.type) { - return true; + // Deal with array by delegating to the component type + if (isArray()) { + return (other.isArray() && getComponentType().isAssignableFrom(other.getComponentType(), true, matchedBefore)); } // Deal with wildcard bounds @@ -340,13 +350,15 @@ public class ResolvableType implements Serializable { } } if (ourResolved == null) { - ourResolved = resolve(Object.class); + ourResolved = toClass(); } Class otherResolved = other.toClass(); // We need an exact type match for generics // List is not assignable from List - if (exactMatch ? !ourResolved.equals(otherResolved) : !ClassUtils.isAssignable(ourResolved, otherResolved)) { + if (exactMatch ? !ourResolved.equals(otherResolved) : + (strict ? !ourResolved.isAssignableFrom(otherResolved) : + !ClassUtils.isAssignable(ourResolved, otherResolved))) { return false; } @@ -357,13 +369,15 @@ public class ResolvableType implements Serializable { if (ourGenerics.length != typeGenerics.length) { return false; } - if (matchedBefore == null) { - matchedBefore = new IdentityHashMap<>(1); - } - matchedBefore.put(this.type, other.type); - for (int i = 0; i < ourGenerics.length; i++) { - if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], matchedBefore)) { - return false; + if (ourGenerics.length > 0) { + if (matchedBefore == null) { + matchedBefore = new IdentityHashMap<>(1); + } + matchedBefore.put(this.type, other.type); + for (int i = 0; i < ourGenerics.length; i++) { + if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], true, matchedBefore)) { + return false; + } } } } diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index e4f62a5c24..424b27388f 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -697,13 +697,20 @@ class ResolvableTypeTests { assertThat(type.getGeneric(0).as(Collection.class).getGeneric(0).as(Collection.class).resolve()).isNull(); } + @Test + void intArrayNotAssignableToIntegerArray() throws Exception { + ResolvableType integerArray = ResolvableType.forField(Fields.class.getField("integerArray")); + ResolvableType intArray = ResolvableType.forField(Fields.class.getField("intArray")); + assertThat(integerArray.isAssignableFrom(intArray)).isFalse(); + assertThat(intArray.isAssignableFrom(integerArray)).isFalse(); + } + @Test void resolveBoundedTypeVariableResult() throws Exception { ResolvableType type = ResolvableType.forMethodReturnType(Methods.class.getMethod("boundedTypeVariableResult")); assertThat(type.resolve()).isEqualTo(CharSequence.class); } - @Test void resolveBoundedTypeVariableWildcardResult() throws Exception { ResolvableType type = ResolvableType.forMethodReturnType(Methods.class.getMethod("boundedTypeVariableWildcardResult")); @@ -718,30 +725,26 @@ class ResolvableTypeTests { @Test void resolveTypeVariableFromSimpleInterfaceType() { - ResolvableType type = ResolvableType.forClass( - MySimpleInterfaceType.class).as(MyInterfaceType.class); + ResolvableType type = ResolvableType.forClass(MySimpleInterfaceType.class).as(MyInterfaceType.class); assertThat(type.resolveGeneric()).isEqualTo(String.class); } @Test void resolveTypeVariableFromSimpleCollectionInterfaceType() { - ResolvableType type = ResolvableType.forClass( - MyCollectionInterfaceType.class).as(MyInterfaceType.class); + ResolvableType type = ResolvableType.forClass(MyCollectionInterfaceType.class).as(MyInterfaceType.class); assertThat(type.resolveGeneric()).isEqualTo(Collection.class); assertThat(type.resolveGeneric(0, 0)).isEqualTo(String.class); } @Test void resolveTypeVariableFromSimpleSuperclassType() { - ResolvableType type = ResolvableType.forClass( - MySimpleSuperclassType.class).as(MySuperclassType.class); + ResolvableType type = ResolvableType.forClass(MySimpleSuperclassType.class).as(MySuperclassType.class); assertThat(type.resolveGeneric()).isEqualTo(String.class); } @Test void resolveTypeVariableFromSimpleCollectionSuperclassType() { - ResolvableType type = ResolvableType.forClass( - MyCollectionSuperclassType.class).as(MySuperclassType.class); + ResolvableType type = ResolvableType.forClass(MyCollectionSuperclassType.class).as(MySuperclassType.class); assertThat(type.resolveGeneric()).isEqualTo(Collection.class); assertThat(type.resolveGeneric(0, 0)).isEqualTo(String.class); } @@ -768,8 +771,7 @@ class ResolvableTypeTests { void resolveTypeVariableFromSuperType() throws Exception { ResolvableType type = ResolvableType.forClass(ExtendsList.class); assertThat(type.resolve()).isEqualTo(ExtendsList.class); - assertThat(type.asCollection().resolveGeneric()) - .isEqualTo(CharSequence.class); + assertThat(type.asCollection().resolveGeneric()).isEqualTo(CharSequence.class); } @Test @@ -1021,6 +1023,7 @@ class ResolvableTypeTests { void isAssignableFromCannotBeResolved() throws Exception { ResolvableType objectType = ResolvableType.forClass(Object.class); ResolvableType unresolvableVariable = ResolvableType.forField(AssignmentBase.class.getField("o")); + assertThat(unresolvableVariable.resolve()).isNull(); assertThatResolvableType(objectType).isAssignableFrom(unresolvableVariable); assertThatResolvableType(unresolvableVariable).isAssignableFrom(objectType); @@ -1294,7 +1297,7 @@ class ResolvableTypeTests { } @Test - void hasUnresolvableGenericsWhenImplementesRawInterface() throws Exception { + void hasUnresolvableGenericsWhenImplementingRawInterface() throws Exception { ResolvableType type = ResolvableType.forClass(MySimpleInterfaceTypeWithImplementsRaw.class); for (ResolvableType generic : type.getGenerics()) { assertThat(generic.resolve()).isNotNull(); @@ -1432,6 +1435,10 @@ class ResolvableTypeTests { public Map, Map> nested; public T[] variableTypeGenericArray; + + public Integer[] integerArray; + + public int[] intArray; }