diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index a6af8986c53..00eab29578f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -561,7 +561,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp @Override public boolean isTypeMatch(String name, Class typeToMatch) throws NoSuchBeanDefinitionException { - return isTypeMatch(name, ResolvableType.forClass(typeToMatch != null ? typeToMatch : Object.class)); + return isTypeMatch(name, ResolvableType.forRawClass(typeToMatch)); } @Override diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 112dbfdffc5..f0238221940 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -412,8 +412,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @Override public String[] getBeanNamesForType(Class type, boolean includeNonSingletons, boolean allowEagerInit) { if (!isConfigurationFrozen() || type == null || !allowEagerInit) { - return doGetBeanNamesForType(ResolvableType.forClass(type != null ? type : Object.class), - includeNonSingletons, allowEagerInit); + return doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, allowEagerInit); } Map, String[]> cache = (includeNonSingletons ? this.allBeanNamesByType : this.singletonBeanNamesByType); @@ -421,7 +420,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto if (resolvedBeanNames != null) { return resolvedBeanNames; } - resolvedBeanNames = doGetBeanNamesForType(ResolvableType.forClass(type), includeNonSingletons, allowEagerInit); + resolvedBeanNames = doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, true); if (ClassUtils.isCacheSafe(type, getBeanClassLoader())) { cache.put(type, resolvedBeanNames); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java index ed0ef011b74..1c6bab20580 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java @@ -17,6 +17,7 @@ package org.springframework.beans.factory; import java.io.Closeable; +import java.io.Serializable; import java.lang.reflect.Field; import java.net.MalformedURLException; import java.security.AccessControlContext; @@ -32,6 +33,7 @@ import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.concurrent.Callable; import javax.annotation.Priority; import javax.security.auth.Subject; @@ -1669,13 +1671,21 @@ public class DefaultListableBeanFactoryTests { @Test public void testGetBeanNamesForTypeBeforeFactoryBeanCreation() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); - lbf.registerBeanDefinition("factoryBean", new RootBeanDefinition(FactoryBeanThatShouldntBeCalled.class.getName())); + lbf.registerBeanDefinition("factoryBean", new RootBeanDefinition(FactoryBeanThatShouldntBeCalled.class)); assertFalse(lbf.containsSingleton("factoryBean")); String[] beanNames = lbf.getBeanNamesForType(Runnable.class, false, false); assertEquals(1, beanNames.length); assertEquals("&factoryBean", beanNames[0]); + beanNames = lbf.getBeanNamesForType(Callable.class, false, false); + assertEquals(1, beanNames.length); + assertEquals("&factoryBean", beanNames[0]); + + beanNames = lbf.getBeanNamesForType(RepositoryFactoryInformation.class, false, false); + assertEquals(1, beanNames.length); + assertEquals("&factoryBean", beanNames[0]); + beanNames = lbf.getBeanNamesForType(FactoryBean.class, false, false); assertEquals(1, beanNames.length); assertEquals("&factoryBean", beanNames[0]); @@ -1684,13 +1694,21 @@ public class DefaultListableBeanFactoryTests { @Test public void testGetBeanNamesForTypeAfterFactoryBeanCreation() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); - lbf.registerBeanDefinition("factoryBean", new RootBeanDefinition(FactoryBeanThatShouldntBeCalled.class.getName())); + lbf.registerBeanDefinition("factoryBean", new RootBeanDefinition(FactoryBeanThatShouldntBeCalled.class)); lbf.getBean("&factoryBean"); String[] beanNames = lbf.getBeanNamesForType(Runnable.class, false, false); assertEquals(1, beanNames.length); assertEquals("&factoryBean", beanNames[0]); + beanNames = lbf.getBeanNamesForType(Callable.class, false, false); + assertEquals(1, beanNames.length); + assertEquals("&factoryBean", beanNames[0]); + + beanNames = lbf.getBeanNamesForType(RepositoryFactoryInformation.class, false, false); + assertEquals(1, beanNames.length); + assertEquals("&factoryBean", beanNames[0]); + beanNames = lbf.getBeanNamesForType(FactoryBean.class, false, false); assertEquals(1, beanNames.length); assertEquals("&factoryBean", beanNames[0]); @@ -2892,10 +2910,24 @@ public class DefaultListableBeanFactoryTests { } - public static class FactoryBeanThatShouldntBeCalled implements FactoryBean, Runnable { + public interface Repository { + } + + + public interface RepositoryFactoryInformation { + } + + + public static abstract class RepositoryFactoryBeanSupport, S, ID extends Serializable> + implements RepositoryFactoryInformation, FactoryBean { + } + + + public static class FactoryBeanThatShouldntBeCalled, S, ID extends Serializable> + extends RepositoryFactoryBeanSupport implements Runnable, Callable { @Override - public Object getObject() { + public T getObject() { throw new IllegalStateException(); } @@ -2913,6 +2945,11 @@ public class DefaultListableBeanFactoryTests { public void run() { throw new IllegalStateException(); } + + @Override + public T call() throws Exception { + throw new IllegalStateException(); + } } 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 d5407537df3..ac17d9373e1 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -76,7 +76,7 @@ import org.springframework.util.StringUtils; * @see #forType(Type) */ @SuppressWarnings("serial") -public final class ResolvableType implements Serializable { +public class ResolvableType implements Serializable { /** * {@code ResolvableType} returned when no value is available. {@code NONE} is used @@ -122,6 +122,17 @@ public final class ResolvableType implements Serializable { private ResolvableType[] generics; + /** + * Private constructor used to create a new {@link ResolvableType} for cache key purposes. + */ + private ResolvableType(Type type, TypeProvider typeProvider, VariableResolver variableResolver) { + this.type = type; + this.typeProvider = typeProvider; + this.variableResolver = variableResolver; + this.componentType = null; + this.resolved = null; + } + /** * Private constructor used to create a new {@link ResolvableType} for resolution purposes. */ @@ -136,14 +147,16 @@ public final class ResolvableType implements Serializable { } /** - * Private constructor used to create a new {@link ResolvableType} for cache key purposes. + * Private constructor used to create a new {@link ResolvableType} on a {@link Class} basis. + * Avoids all {@code instanceof} checks in order to create a straight {@link Class} wrapper. + * @since 4.2 */ - private ResolvableType(Type type, TypeProvider typeProvider, VariableResolver variableResolver) { - this.type = type; - this.typeProvider = typeProvider; - this.variableResolver = variableResolver; + private ResolvableType(Class sourceClass) { + this.resolved = (sourceClass != null ? sourceClass : Object.class); + this.type = this.resolved; + this.typeProvider = null; + this.variableResolver = null; this.componentType = null; - this.resolved = null; } @@ -160,6 +173,9 @@ public final class ResolvableType implements Serializable { * otherwise {@code null}. */ public Class getRawClass() { + if (this.type == this.resolved) { + return this.resolved; + } Type rawType = this.type; if (rawType instanceof ParameterizedType) { rawType = ((ParameterizedType) rawType).getRawType(); @@ -619,7 +635,7 @@ public final class ResolvableType implements Serializable { return EMPTY_TYPES_ARRAY; } if (this.generics == null) { - if (this.type instanceof Class) { + if (this.type instanceof Class) { Class typeClass = (Class) this.type; this.generics = forTypes(SerializableTypeWrapper.forTypeParameters(typeClass), this.variableResolver); } @@ -712,7 +728,7 @@ public final class ResolvableType implements Serializable { } private Class resolveClass() { - if (this.type instanceof Class || this.type == null) { + if (this.type instanceof Class || this.type == null) { return (Class) this.type; } if (this.type instanceof GenericArrayType) { @@ -783,48 +799,20 @@ public final class ResolvableType implements Serializable { return null; } - /** - * Return a String representation of this type in its fully resolved form - * (including any generic parameters). - */ - @Override - public String toString() { - if (isArray()) { - return getComponentType() + "[]"; - } - if (this.resolved == null) { - return "?"; - } - if (this.type instanceof TypeVariable) { - TypeVariable variable = (TypeVariable) this.type; - if (this.variableResolver == null || this.variableResolver.resolveVariable(variable) == null) { - // Don't bother with variable boundaries for toString()... - // Can cause infinite recursions in case of self-references - return "?"; - } - } - StringBuilder result = new StringBuilder(this.resolved.getName()); - if (hasGenerics()) { - result.append('<'); - result.append(StringUtils.arrayToDelimitedString(getGenerics(), ", ")); - result.append('>'); - } - return result.toString(); - } @Override - public boolean equals(Object obj) { - if (this == obj) { + public boolean equals(Object other) { + if (this == other) { return true; } - if (!(obj instanceof ResolvableType)) { + if (!(other instanceof ResolvableType)) { return false; } - ResolvableType other = (ResolvableType) obj; - return (ObjectUtils.nullSafeEquals(this.type, other.type) && - ObjectUtils.nullSafeEquals(getSource(), other.getSource()) && - variableResolverSourceEquals(other.variableResolver) && - ObjectUtils.nullSafeEquals(this.componentType, other.componentType)); + ResolvableType otherType = (ResolvableType) other; + return (ObjectUtils.nullSafeEquals(this.type, otherType.type) && + ObjectUtils.nullSafeEquals(getSource(), otherType.getSource()) && + variableResolverSourceEquals(otherType.variableResolver) && + ObjectUtils.nullSafeEquals(this.componentType, otherType.componentType)); } @Override @@ -836,23 +824,6 @@ public final class ResolvableType implements Serializable { return hashCode; } - /** - * Custom serialization support for {@link #NONE}. - */ - private Object readResolve() throws ObjectStreamException { - return (this.type == null ? NONE : this); - } - - /** - * Adapts this {@link ResolvableType} to a {@link VariableResolver}. - */ - VariableResolver asVariableResolver() { - if (this == NONE) { - return null; - } - return new DefaultVariableResolver(); - } - private boolean variableResolverSourceEquals(VariableResolver other) { if (this.variableResolver == null) { return (other == null); @@ -871,31 +842,94 @@ public final class ResolvableType implements Serializable { return hashCode; } - private static ResolvableType[] forTypes(Type[] types, VariableResolver owner) { - ResolvableType[] result = new ResolvableType[types.length]; - for (int i = 0; i < types.length; i++) { - result[i] = forType(types[i], owner); + /** + * Adapts this {@link ResolvableType} to a {@link VariableResolver}. + */ + VariableResolver asVariableResolver() { + if (this == NONE) { + return null; } - return result; + return new DefaultVariableResolver(); } /** - * Return a {@link ResolvableType} for the specified {@link Class}. For example - * {@code ResolvableType.forClass(MyArrayList.class)}. - * @param sourceClass the source class (must not be {@code null} + * Custom serialization support for {@link #NONE}. + */ + private Object readResolve() throws ObjectStreamException { + return (this.type == null ? NONE : this); + } + + /** + * Return a String representation of this type in its fully resolved form + * (including any generic parameters). + */ + @Override + public String toString() { + if (isArray()) { + return getComponentType() + "[]"; + } + if (this.resolved == null) { + return "?"; + } + if (this.type instanceof TypeVariable) { + TypeVariable variable = (TypeVariable) this.type; + if (this.variableResolver == null || this.variableResolver.resolveVariable(variable) == null) { + // Don't bother with variable boundaries for toString()... + // Can cause infinite recursions in case of self-references + return "?"; + } + } + StringBuilder result = new StringBuilder(this.resolved.getName()); + if (hasGenerics()) { + result.append('<'); + result.append(StringUtils.arrayToDelimitedString(getGenerics(), ", ")); + result.append('>'); + } + return result.toString(); + } + + + // Factory methods + + /** + * Return a {@link ResolvableType} for the specified {@link Class}, + * using the full generic type information for assignability checks. + * For example: {@code ResolvableType.forClass(MyArrayList.class)}. + * @param sourceClass the source class ({@code null} is semantically + * equivalent to {@code Object.class} for typical use cases here} * @return a {@link ResolvableType} for the specified class * @see #forClass(Class, Class) * @see #forClassWithGenerics(Class, Class...) */ public static ResolvableType forClass(Class sourceClass) { - Assert.notNull(sourceClass, "Source class must not be null"); - return forType(sourceClass); + return new ResolvableType(sourceClass); } /** - * Return a {@link ResolvableType} for the specified {@link Class} with a given - * implementation. For example - * {@code ResolvableType.forClass(List.class, MyArrayList.class)}. + * Return a {@link ResolvableType} for the specified {@link Class}, doing + * assignability checks against the raw class only (analogous to + * {@link Class#isAssignableFrom}, which this serves as a wrapper for. + * For example: {@code ResolvableType.forClass(MyArrayList.class)}. + * @param sourceClass the source class ({@code null} is semantically + * equivalent to {@code Object.class} for typical use cases here} + * @return a {@link ResolvableType} for the specified class + * @since 4.2 + * @see #forClass(Class) + * @see #getRawClass() + */ + public static ResolvableType forRawClass(Class sourceClass) { + return new ResolvableType(sourceClass) { + @Override + public boolean isAssignableFrom(Class other) { + return ClassUtils.isAssignable(getRawClass(), other); + } + }; + } + + /** + * Return a {@link ResolvableType} for the specified {@link Class} + * with a given implementation. + * For example: {@code ResolvableType.forClass(List.class, MyArrayList.class)}. * @param sourceClass the source class (must not be {@code null} * @param implementationClass the implementation class * @return a {@link ResolvableType} for the specified class backed by the given @@ -909,6 +943,47 @@ public final class ResolvableType implements Serializable { return (asType == NONE ? forType(sourceClass) : asType); } + /** + * Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics. + * @param sourceClass the source class + * @param generics the generics of the class + * @return a {@link ResolvableType} for the specific class and generics + * @see #forClassWithGenerics(Class, ResolvableType...) + */ + public static ResolvableType forClassWithGenerics(Class sourceClass, Class... generics) { + Assert.notNull(sourceClass, "Source class must not be null"); + Assert.notNull(generics, "Generics must not be null"); + ResolvableType[] resolvableGenerics = new ResolvableType[generics.length]; + for (int i = 0; i < generics.length; i++) { + resolvableGenerics[i] = forClass(generics[i]); + } + return forClassWithGenerics(sourceClass, resolvableGenerics); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics. + * @param sourceClass the source class + * @param generics the generics of the class + * @return a {@link ResolvableType} for the specific class and generics + * @see #forClassWithGenerics(Class, Class...) + */ + public static ResolvableType forClassWithGenerics(Class sourceClass, ResolvableType... generics) { + Assert.notNull(sourceClass, "Source class must not be null"); + Assert.notNull(generics, "Generics must not be null"); + TypeVariable[] variables = sourceClass.getTypeParameters(); + Assert.isTrue(variables.length == generics.length, "Mismatched number of generics specified"); + + Type[] arguments = new Type[generics.length]; + for (int i = 0; i < generics.length; i++) { + ResolvableType generic = generics[i]; + Type argument = (generic != null ? generic.getType() : null); + arguments[i] = (argument != null ? argument : variables[i]); + } + + ParameterizedType syntheticType = new SyntheticParameterizedType(sourceClass, arguments); + return forType(syntheticType, new TypeVariablesVariableResolver(variables, generics)); + } + /** * Return a {@link ResolvableType} for the specified {@link Field}. * @param field the source field @@ -1123,50 +1198,17 @@ public final class ResolvableType implements Serializable { * @return a {@link ResolvableType} as an array of the specified component type */ public static ResolvableType forArrayComponent(ResolvableType componentType) { - Assert.notNull(componentType, "ComponentType must not be null"); + Assert.notNull(componentType, "componentType must not be null"); Class arrayClass = Array.newInstance(componentType.resolve(), 0).getClass(); return new ResolvableType(arrayClass, null, null, componentType); } - /** - * Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics. - * @param sourceClass the source class - * @param generics the generics of the class - * @return a {@link ResolvableType} for the specific class and generics - * @see #forClassWithGenerics(Class, ResolvableType...) - */ - public static ResolvableType forClassWithGenerics(Class sourceClass, Class... generics) { - Assert.notNull(sourceClass, "Source class must not be null"); - Assert.notNull(generics, "Generics must not be null"); - ResolvableType[] resolvableGenerics = new ResolvableType[generics.length]; - for (int i = 0; i < generics.length; i++) { - resolvableGenerics[i] = forClass(generics[i]); - } - return forClassWithGenerics(sourceClass, resolvableGenerics); - } - - /** - * Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics. - * @param sourceClass the source class - * @param generics the generics of the class - * @return a {@link ResolvableType} for the specific class and generics - * @see #forClassWithGenerics(Class, Class...) - */ - public static ResolvableType forClassWithGenerics(Class sourceClass, ResolvableType... generics) { - Assert.notNull(sourceClass, "Source class must not be null"); - Assert.notNull(generics, "Generics must not be null"); - TypeVariable[] variables = sourceClass.getTypeParameters(); - Assert.isTrue(variables.length == generics.length, "Mismatched number of generics specified"); - - Type[] arguments = new Type[generics.length]; - for (int i = 0; i < generics.length; i++) { - ResolvableType generic = generics[i]; - Type argument = (generic != null ? generic.getType() : null); - arguments[i] = (argument != null ? argument : variables[i]); + private static ResolvableType[] forTypes(Type[] types, VariableResolver owner) { + ResolvableType[] result = new ResolvableType[types.length]; + for (int i = 0; i < types.length; i++) { + result[i] = forType(types[i], owner); } - - ParameterizedType syntheticType = new SyntheticParameterizedType(sourceClass, arguments); - return forType(syntheticType, new TypeVariablesVariableResolver(variables, generics)); + return result; } /** @@ -1223,15 +1265,15 @@ public final class ResolvableType implements Serializable { return NONE; } - // Purge empty entries on access since we don't have a clean-up thread or the like. - cache.purgeUnreferencedEntries(); - // For simple Class references, build the wrapper right away - // no expensive resolution necessary, so not worth caching... - if (type instanceof Class) { + if (type instanceof Class) { return new ResolvableType(type, typeProvider, variableResolver, null); } + // Purge empty entries on access since we don't have a clean-up thread or the like. + cache.purgeUnreferencedEntries(); + // Check the cache - we may have a ResolvableType which has been resolved before... ResolvableType key = new ResolvableType(type, typeProvider, variableResolver); ResolvableType resolvableType = cache.get(key); 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 8083e2a3451..d24cceb88da 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -101,13 +101,36 @@ public class ResolvableTypeTests { public void forClass() throws Exception { ResolvableType type = ResolvableType.forClass(ExtendsList.class); assertThat(type.getType(), equalTo((Type) ExtendsList.class)); + assertThat(type.getRawClass(), equalTo(ExtendsList.class)); + assertTrue(type.isAssignableFrom(ExtendsList.class)); + assertFalse(type.isAssignableFrom(ArrayList.class)); } @Test - public void forClassMustNotBeNull() throws Exception { - this.thrown.expect(IllegalArgumentException.class); - this.thrown.expectMessage("Source class must not be null"); - ResolvableType.forClass(null); + public void forClassWithNull() throws Exception { + ResolvableType type = ResolvableType.forClass(null); + assertThat(type.getType(), equalTo((Type) Object.class)); + assertThat(type.getRawClass(), equalTo(Object.class)); + assertTrue(type.isAssignableFrom(Object.class)); + assertTrue(type.isAssignableFrom(String.class)); + } + + @Test + public void forRawClass() throws Exception { + ResolvableType type = ResolvableType.forRawClass(ExtendsList.class); + assertThat(type.getType(), equalTo((Type) ExtendsList.class)); + assertThat(type.getRawClass(), equalTo(ExtendsList.class)); + assertTrue(type.isAssignableFrom(ExtendsList.class)); + assertFalse(type.isAssignableFrom(ArrayList.class)); + } + + @Test + public void forRawClassWithNull() throws Exception { + ResolvableType type = ResolvableType.forRawClass(null); + assertThat(type.getType(), equalTo((Type) Object.class)); + assertThat(type.getRawClass(), equalTo(Object.class)); + assertTrue(type.isAssignableFrom(Object.class)); + assertTrue(type.isAssignableFrom(String.class)); } @Test