Support for matching partial generics

Closes gh-20727
This commit is contained in:
Juergen Hoeller 2024-02-15 16:51:09 +01:00
parent d4e8daaede
commit 7e67da8a26
3 changed files with 45 additions and 15 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 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.
@ -144,12 +144,16 @@ public class GenericTypeAwareAutowireCandidateResolver extends SimpleAutowireCan
if (cacheType) {
rbd.targetType = targetType;
}
if (descriptor.fallbackMatchAllowed() &&
(targetType.hasUnresolvableGenerics() || targetType.resolve() == Properties.class)) {
if (descriptor.fallbackMatchAllowed()) {
// Fallback matches allow unresolvable generics, e.g. plain HashMap to Map<String,String>;
// and pragmatically also java.util.Properties to any Map (since despite formally being a
// Map<Object,Object>, java.util.Properties is usually perceived as a Map<String,String>).
return true;
if (targetType.hasUnresolvableGenerics()) {
return dependencyType.isAssignableFromResolvedPart(targetType);
}
else if (targetType.resolve() == Properties.class) {
return true;
}
}
// Full check for complex generic type match...
return dependencyType.isAssignableFrom(targetType);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 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.
@ -32,8 +32,8 @@ class Spr16179Tests {
void repro() {
try (AnnotationConfigApplicationContext bf = new AnnotationConfigApplicationContext(AssemblerConfig.class, AssemblerInjection.class)) {
assertThat(bf.getBean(AssemblerInjection.class).assembler0).isSameAs(bf.getBean("someAssembler"));
// assertNull(bf.getBean(AssemblerInjection.class).assembler1); TODO: accidental match
// assertNull(bf.getBean(AssemblerInjection.class).assembler2);
assertThat(bf.getBean(AssemblerInjection.class).assembler1).isNull();
assertThat(bf.getBean(AssemblerInjection.class).assembler2).isSameAs(bf.getBean("pageAssembler"));
assertThat(bf.getBean(AssemblerInjection.class).assembler3).isSameAs(bf.getBean("pageAssembler"));
assertThat(bf.getBean(AssemblerInjection.class).assembler4).isSameAs(bf.getBean("pageAssembler"));
assertThat(bf.getBean(AssemblerInjection.class).assembler5).isSameAs(bf.getBean("pageAssembler"));

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 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.
@ -265,7 +265,7 @@ public class ResolvableType implements Serializable {
public boolean isAssignableFrom(Class<?> other) {
// 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));
isAssignableFrom(forClass(other), false, null, false));
}
/**
@ -280,10 +280,24 @@ public class ResolvableType implements Serializable {
* {@code ResolvableType}; {@code false} otherwise
*/
public boolean isAssignableFrom(ResolvableType other) {
return isAssignableFrom(other, false, null);
return isAssignableFrom(other, false, null, false);
}
private boolean isAssignableFrom(ResolvableType other, boolean strict, @Nullable Map<Type, Type> matchedBefore) {
/**
* Determine whether this {@code ResolvableType} is assignable from the
* specified other type, as far as the other type is actually resolvable.
* @param other the type to be checked against (as a {@code ResolvableType})
* @return {@code true} if the specified other type can be assigned to this
* {@code ResolvableType} as far as it is resolvable; {@code false} otherwise
* @since 6.2
*/
public boolean isAssignableFromResolvedPart(ResolvableType other) {
return isAssignableFrom(other, false, null, true);
}
private boolean isAssignableFrom(ResolvableType other, boolean strict,
@Nullable Map<Type, Type> matchedBefore, boolean upUntilUnresolvable) {
Assert.notNull(other, "ResolvableType must not be null");
// If we cannot resolve types, we are not assignable
@ -305,7 +319,12 @@ public class ResolvableType implements Serializable {
// Deal with array by delegating to the component type
if (isArray()) {
return (other.isArray() && getComponentType().isAssignableFrom(other.getComponentType(), true, matchedBefore));
return (other.isArray() && getComponentType().isAssignableFrom(
other.getComponentType(), true, matchedBefore, upUntilUnresolvable));
}
if (upUntilUnresolvable && other.isUnresolvableTypeVariable()) {
return true;
}
// Deal with wildcard bounds
@ -314,8 +333,15 @@ public class ResolvableType implements Serializable {
// In the form X is assignable to <? extends Number>
if (typeBounds != null) {
return (ourBounds != null && ourBounds.isSameKind(typeBounds) &&
ourBounds.isAssignableFrom(typeBounds.getBounds()));
if (ourBounds != null) {
return (ourBounds.isSameKind(typeBounds) && ourBounds.isAssignableFrom(typeBounds.getBounds()));
}
else if (upUntilUnresolvable) {
return typeBounds.isAssignableFrom(this);
}
else {
return false;
}
}
// In the form <? extends Number> is assignable to X...
@ -376,7 +402,7 @@ public class ResolvableType implements Serializable {
}
matchedBefore.put(this.type, other.type);
for (int i = 0; i < ourGenerics.length; i++) {
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], true, matchedBefore)) {
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], true, matchedBefore, upUntilUnresolvable)) {
return false;
}
}