From 1603c4ab2fc49d1bd65f55e35ca899044835ca94 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 24 Jul 2018 00:42:03 +0200 Subject: [PATCH] Programmatic ObjectProvider retrieval through BeanFactory API Introduces getBeanProvider(Class) and getBeanProvider(ResolvableType), also narrowing getBean(String, Class) and isTypeMatch(String, Class) to a non-null Class argument and enriching NoUniqueBeanDefinitionException with a full ResolvableType. In addition, ObjectProvider supports iterable/stream access for collection-style resolution of multiple matching beans now, and collection injection falls back to an empty collection in a single-constructor case with non-null arguments. Issue: SPR-17075 Issue: SPR-11419 Issue: SPR-15338 --- .../beans/factory/BeanFactory.java | 34 ++- .../NoUniqueBeanDefinitionException.java | 24 ++ .../beans/factory/ObjectProvider.java | 31 ++- .../factory/config/DependencyDescriptor.java | 37 ++++ .../factory/support/AbstractBeanFactory.java | 9 +- .../factory/support/ConstructorResolver.java | 48 ++-- .../support/DefaultListableBeanFactory.java | 134 ++++++++++-- .../support/StaticListableBeanFactory.java | 70 ++++++ .../DefaultListableBeanFactoryTests.java | 201 ++++++++++++++++- ...wiredAnnotationBeanPostProcessorTests.java | 207 +++++++++++++++--- .../support/BeanFactoryGenericsTests.java | 65 +++++- .../support/AbstractApplicationContext.java | 17 +- .../jndi/support/SimpleJndiBeanFactory.java | 47 +++- ...notationConfigApplicationContextTests.java | 6 - .../ConfigurationClassPostProcessorTests.java | 71 ++++++ .../setup/StubWebApplicationContext.java | 17 +- 16 files changed, 918 insertions(+), 100 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java index d3bfa88f66..3ccbda3aea 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -148,16 +148,13 @@ public interface BeanFactory { *

Translates aliases back to the corresponding canonical bean name. * Will ask the parent factory if the bean cannot be found in this factory instance. * @param name the name of the bean to retrieve - * @param requiredType type the bean must match. Can be an interface or superclass - * of the actual class, or {@code null} for any match. For example, if the value - * is {@code Object.class}, this method will succeed whatever the class of the - * returned instance. + * @param requiredType type the bean must match; can be an interface or superclass * @return an instance of the bean * @throws NoSuchBeanDefinitionException if there is no such bean definition * @throws BeanNotOfRequiredTypeException if the bean is not of the required type * @throws BeansException if the bean could not be created */ - T getBean(String name, @Nullable Class requiredType) throws BeansException; + T getBean(String name, Class requiredType) throws BeansException; /** * Return an instance, which may be shared or independent, of the specified bean. @@ -181,8 +178,7 @@ public interface BeanFactory { * but may also be translated into a conventional by-name lookup based on the name * of the given type. For more extensive retrieval operations across sets of beans, * use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}. - * @param requiredType type the bean must match; can be an interface or superclass. - * {@code null} is disallowed. + * @param requiredType type the bean must match; can be an interface or superclass * @return an instance of the single bean matching the required type * @throws NoSuchBeanDefinitionException if no bean of the given type was found * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found @@ -200,8 +196,7 @@ public interface BeanFactory { * but may also be translated into a conventional by-name lookup based on the name * of the given type. For more extensive retrieval operations across sets of beans, * use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}. - * @param requiredType type the bean must match; can be an interface or superclass. - * {@code null} is disallowed. + * @param requiredType type the bean must match; can be an interface or superclass * @param args arguments to use when creating a bean instance using explicit arguments * (only applied when creating a new instance as opposed to retrieving an existing one) * @return an instance of the bean @@ -213,6 +208,23 @@ public interface BeanFactory { */ T getBean(Class requiredType, Object... args) throws BeansException; + /** + * Return an provider for the specified bean, allowing for lazy on-demand retrieval + * of instances, including availability and uniqueness options. + * @param requiredType type the bean must match; can be an interface or superclass + * @return a corresponding provider handle + * @since 5.1 + */ + ObjectProvider getBeanProvider(Class requiredType); + + /** + * Return an provider for the specified bean, allowing for lazy on-demand retrieval + * of instances, including availability and uniqueness options. + * @param requiredType type the bean must match; can be a generic type declaration + * @return a corresponding provider handle + * @since 5.1 + */ + ObjectProvider getBeanProvider(ResolvableType requiredType); /** * Does this bean factory contain a bean definition or externally registered singleton @@ -298,7 +310,7 @@ public interface BeanFactory { * @see #getBean * @see #getType */ - boolean isTypeMatch(String name, @Nullable Class typeToMatch) throws NoSuchBeanDefinitionException; + boolean isTypeMatch(String name, Class typeToMatch) throws NoSuchBeanDefinitionException; /** * Determine the type of the bean with the given name. More specifically, diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/NoUniqueBeanDefinitionException.java b/spring-beans/src/main/java/org/springframework/beans/factory/NoUniqueBeanDefinitionException.java index d7ebe487ab..f94e9d8805 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/NoUniqueBeanDefinitionException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/NoUniqueBeanDefinitionException.java @@ -19,6 +19,7 @@ package org.springframework.beans.factory; import java.util.Arrays; import java.util.Collection; +import org.springframework.core.ResolvableType; import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; @@ -72,6 +73,29 @@ public class NoUniqueBeanDefinitionException extends NoSuchBeanDefinitionExcepti this(type, Arrays.asList(beanNamesFound)); } + /** + * Create a new {@code NoUniqueBeanDefinitionException}. + * @param type required type of the non-unique bean + * @param beanNamesFound the names of all matching beans (as a Collection) + * @since 5.1 + */ + public NoUniqueBeanDefinitionException(ResolvableType type, Collection beanNamesFound) { + super(type, "expected single matching bean but found " + beanNamesFound.size() + ": " + + StringUtils.collectionToCommaDelimitedString(beanNamesFound)); + this.numberOfBeansFound = beanNamesFound.size(); + this.beanNamesFound = beanNamesFound; + } + + /** + * Create a new {@code NoUniqueBeanDefinitionException}. + * @param type required type of the non-unique bean + * @param beanNamesFound the names of all matching beans (as an array) + * @since 5.1 + */ + public NoUniqueBeanDefinitionException(ResolvableType type, String... beanNamesFound) { + this(type, Arrays.asList(beanNamesFound)); + } + /** * Return the number of beans found when only one matching bean was expected. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java index 209444bcfa..7f4ef21cb4 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java @@ -16,8 +16,10 @@ package org.springframework.beans.factory; +import java.util.Iterator; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.stream.Stream; import org.springframework.beans.BeansException; import org.springframework.lang.Nullable; @@ -26,11 +28,15 @@ import org.springframework.lang.Nullable; * A variant of {@link ObjectFactory} designed specifically for injection points, * allowing for programmatic optionality and lenient not-unique handling. * + *

As of 5.1, this interface extends {@link Iterable} and provides {@link Stream} + * support. It can be therefore be used in {@code for} loops, provides {@link #forEach} + * iteration and allows for collection-style {@link #stream} access. + * * @author Juergen Hoeller * @since 4.3 * @param the object type */ -public interface ObjectProvider extends ObjectFactory { +public interface ObjectProvider extends ObjectFactory, Iterable { /** * Return an instance (possibly shared or independent) of the object @@ -130,4 +136,27 @@ public interface ObjectProvider extends ObjectFactory { } } + /** + * Return an {@link Iterator} over resolved object instances. + *

The default implementation delegates to {@link #stream()}. + * @since 5.1 + * @see #stream() + */ + @Override + default Iterator iterator() { + return stream().iterator(); + } + + /** + * Return a sequential {@link Stream} over resolved object instances. + *

The default implementation returns a stream of one element or an + * empty stream if not available, resolved via {@link #getIfAvailable()}. + * @since 5.1 + * @see #iterator() + */ + default Stream stream() { + T instance = getIfAvailable(); + return (instance != null ? Stream.of(instance) : Stream.empty()); + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java index 32cf7843d8..3e1faf1565 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java @@ -199,6 +199,41 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable return this.eager; } + /** + * Return whether this descriptor allows for stream-style access to + * result instances. + *

By default, dependencies are strictly resolved to the declaration of + * the injection point and therefore only resolve multiple entries if the + * injection point is declared as an array, collection or map. This is + * indicated by returning {@code false} here. + *

Overriding this method to return {@code true} indicates that the + * injection point declares the bean type but the resolution is meant to + * end up in a {@link java.util.stream.Stream} for the declared bean type, + * with the caller handling the multi-instance case for the injection point. + * @since 5.1 + */ + public boolean isStreamAccess() { + return false; + } + + /** + * Resolve the specified not-unique scenario: by default, + * throwing a {@link NoUniqueBeanDefinitionException}. + *

Subclasses may override this to select one of the instances or + * to opt out with no result at all through returning {@code null}. + * @param type the requested bean type + * @param matchingBeans a map of bean names and corresponding bean + * instances which have been pre-selected for the given type + * (qualifiers etc already applied) + * @return a bean instance to proceed with, or {@code null} for none + * @throws BeansException in case of the not-unique scenario being fatal + * @since 5.1 + */ + @Nullable + public Object resolveNotUnique(ResolvableType type, Map matchingBeans) throws BeansException { + throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet()); + } + /** * Resolve the specified not-unique scenario: by default, * throwing a {@link NoUniqueBeanDefinitionException}. @@ -211,7 +246,9 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable * @return a bean instance to proceed with, or {@code null} for none * @throws BeansException in case of the not-unique scenario being fatal * @since 4.3 + * @deprecated as of 5.1, in favor of {@link #resolveNotUnique(ResolvableType, Map)} */ + @Deprecated @Nullable public Object resolveNotUnique(Class type, Map matchingBeans) throws BeansException { throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet()); 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 869f0fc642..aa7da4c04c 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 @@ -200,7 +200,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp } @Override - public T getBean(String name, @Nullable Class requiredType) throws BeansException { + public T getBean(String name, Class requiredType) throws BeansException { return doGetBean(name, requiredType, null, false); } @@ -277,10 +277,13 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp // Delegation to parent with explicit args. return (T) parentBeanFactory.getBean(nameToLookup, args); } - else { + else if (requiredType != null) { // No args -> delegate to standard getBean method. return parentBeanFactory.getBean(nameToLookup, requiredType); } + else { + return (T) parentBeanFactory.getBean(nameToLookup); + } } if (!typeCheckOnly) { @@ -586,7 +589,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp } @Override - public boolean isTypeMatch(String name, @Nullable Class typeToMatch) throws NoSuchBeanDefinitionException { + public boolean isTypeMatch(String name, Class typeToMatch) throws NoSuchBeanDefinitionException { return isTypeMatch(name, ResolvableType.forRawClass(typeToMatch)); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java index 0e1e476a1d..2369fc4043 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -43,10 +43,12 @@ import org.springframework.beans.TypeMismatchException; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.InjectionPoint; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.core.CollectionFactory; import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.core.NamedThreadLocal; @@ -133,7 +135,7 @@ class ConstructorResolver { } } if (argsToResolve != null) { - argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve); + argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true); } } @@ -195,7 +197,7 @@ class ConstructorResolver { } } argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames, - getUserDeclaredConstructor(candidate), autowiring); + getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1); } catch (UnsatisfiedDependencyException ex) { if (logger.isTraceEnabled()) { @@ -407,7 +409,7 @@ class ConstructorResolver { } } if (argsToResolve != null) { - argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve); + argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve, true); } } @@ -471,8 +473,8 @@ class ConstructorResolver { if (pnd != null) { paramNames = pnd.getParameterNames(candidate); } - argsHolder = createArgumentArray( - beanName, mbd, resolvedValues, bw, paramTypes, paramNames, candidate, autowiring); + argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, + paramTypes, paramNames, candidate, autowiring, candidates.length == 1); } catch (UnsatisfiedDependencyException ex) { if (logger.isTraceEnabled()) { @@ -654,7 +656,7 @@ class ConstructorResolver { private ArgumentsHolder createArgumentArray( String beanName, RootBeanDefinition mbd, @Nullable ConstructorArgumentValues resolvedValues, BeanWrapper bw, Class[] paramTypes, @Nullable String[] paramNames, Executable executable, - boolean autowiring) throws UnsatisfiedDependencyException { + boolean autowiring, boolean fallback) throws UnsatisfiedDependencyException { TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); TypeConverter converter = (customConverter != null ? customConverter : bw); @@ -720,8 +722,8 @@ class ConstructorResolver { "] - did you specify the correct bean references as arguments?"); } try { - Object autowiredArgument = - resolveAutowiredArgument(methodParam, beanName, autowiredBeanNames, converter); + Object autowiredArgument = resolveAutowiredArgument( + methodParam, beanName, autowiredBeanNames, converter, fallback); args.rawArguments[paramIndex] = autowiredArgument; args.arguments[paramIndex] = autowiredArgument; args.preparedArguments[paramIndex] = new AutowiredArgumentMarker(); @@ -749,8 +751,8 @@ class ConstructorResolver { /** * Resolve the prepared arguments stored in the given bean definition. */ - private Object[] resolvePreparedArguments( - String beanName, RootBeanDefinition mbd, BeanWrapper bw, Executable executable, Object[] argsToResolve) { + private Object[] resolvePreparedArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw, + Executable executable, Object[] argsToResolve, boolean fallback) { TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); TypeConverter converter = (customConverter != null ? customConverter : bw); @@ -764,7 +766,7 @@ class ConstructorResolver { MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex); GenericTypeResolver.resolveParameterType(methodParam, executable.getDeclaringClass()); if (argValue instanceof AutowiredArgumentMarker) { - argValue = resolveAutowiredArgument(methodParam, beanName, null, converter); + argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, fallback); } else if (argValue instanceof BeanMetadataElement) { argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue); @@ -806,17 +808,33 @@ class ConstructorResolver { */ @Nullable protected Object resolveAutowiredArgument(MethodParameter param, String beanName, - @Nullable Set autowiredBeanNames, TypeConverter typeConverter) { + @Nullable Set autowiredBeanNames, TypeConverter typeConverter, boolean fallback) { - if (InjectionPoint.class.isAssignableFrom(param.getParameterType())) { + Class paramType = param.getParameterType(); + if (InjectionPoint.class.isAssignableFrom(paramType)) { InjectionPoint injectionPoint = currentInjectionPoint.get(); if (injectionPoint == null) { throw new IllegalStateException("No current InjectionPoint available for " + param); } return injectionPoint; } - return this.beanFactory.resolveDependency( - new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter); + try { + return this.beanFactory.resolveDependency( + new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter); + } + catch (NoSuchBeanDefinitionException ex) { + if (fallback) { + // Single constructor or factory method -> let's return an + // empty collection for a non-null collection parameter. + if (CollectionFactory.isApproximableCollectionType(paramType)) { + return CollectionFactory.createCollection(paramType, 0); + } + else if (CollectionFactory.isApproximableMapType(paramType)) { + return CollectionFactory.createMap(paramType, 0); + } + } + throw ex; + } } static InjectionPoint setCurrentInjectionPoint(@Nullable InjectionPoint injectionPoint) { 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 1d9e0e68c2..2ed1c5aba3 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 @@ -40,6 +40,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; import javax.inject.Provider; import org.springframework.beans.BeanUtils; @@ -333,17 +334,80 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto return getBean(requiredType, (Object[]) null); } + @SuppressWarnings("unchecked") @Override public T getBean(Class requiredType, @Nullable Object... args) throws BeansException { - NamedBeanHolder namedBean = resolveNamedBean(requiredType, args); + Object resolved = resolveBean(ResolvableType.forRawClass(requiredType), args, false); + if (resolved == null) { + throw new NoSuchBeanDefinitionException(requiredType); + } + return (T) resolved; + } + + @Override + public ObjectProvider getBeanProvider(Class requiredType) throws BeansException { + return getBeanProvider(ResolvableType.forRawClass(requiredType)); + } + + @SuppressWarnings("unchecked") + @Override + public ObjectProvider getBeanProvider(ResolvableType requiredType) { + return new BeanObjectProvider() { + @Override + public T getObject() throws BeansException { + T resolved = resolveBean(requiredType, null, false); + if (resolved == null) { + throw new NoSuchBeanDefinitionException(requiredType); + } + return resolved; + } + @Override + public T getObject(Object... args) throws BeansException { + T resolved = resolveBean(requiredType, args, false); + if (resolved == null) { + throw new NoSuchBeanDefinitionException(requiredType); + } + return resolved; + } + @Override + @Nullable + public T getIfAvailable() throws BeansException { + return resolveBean(requiredType, null, false); + } + @Override + @Nullable + public T getIfUnique() throws BeansException { + return resolveBean(requiredType, null, true); + } + @Override + public Stream stream() { + return Arrays.stream(getBeanNamesForType(requiredType)) + .map(name -> (T) getBean(name)) + .filter(bean -> !(bean instanceof NullBean)); + } + }; + } + + @Nullable + private T resolveBean(ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) { + NamedBeanHolder namedBean = resolveNamedBean(requiredType, args, nonUniqueAsNull); if (namedBean != null) { return namedBean.getBeanInstance(); } BeanFactory parent = getParentBeanFactory(); - if (parent != null) { - return (args != null ? parent.getBean(requiredType, args) : parent.getBean(requiredType)); + if (parent instanceof DefaultListableBeanFactory) { + return ((DefaultListableBeanFactory) parent).resolveBean(requiredType, args, nonUniqueAsNull); } - throw new NoSuchBeanDefinitionException(requiredType); + else if (parent != null) { + ObjectProvider parentProvider = parent.getBeanProvider(requiredType); + if (args != null) { + return parentProvider.getObject(args); + } + else { + return (nonUniqueAsNull ? parentProvider.getIfUnique() : parentProvider.getIfAvailable()); + } + } + return null; } @@ -978,7 +1042,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @Override public NamedBeanHolder resolveNamedBean(Class requiredType) throws BeansException { - NamedBeanHolder namedBean = resolveNamedBean(requiredType, (Object[]) null); + NamedBeanHolder namedBean = resolveNamedBean(ResolvableType.forRawClass(requiredType), null, false); if (namedBean != null) { return namedBean; } @@ -991,8 +1055,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @SuppressWarnings("unchecked") @Nullable - private NamedBeanHolder resolveNamedBean(Class requiredType, @Nullable Object... args) throws BeansException { + private NamedBeanHolder resolveNamedBean( + ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) throws BeansException { + Assert.notNull(requiredType, "Required type must not be null"); + Class clazz = requiredType.getRawClass(); + Assert.notNull(clazz, "Required type must have a raw Class"); String[] candidateNames = getBeanNamesForType(requiredType); if (candidateNames.length > 1) { @@ -1009,7 +1077,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto if (candidateNames.length == 1) { String beanName = candidateNames[0]; - return new NamedBeanHolder<>(beanName, getBean(beanName, requiredType, args)); + return new NamedBeanHolder<>(beanName, (T) getBean(beanName, clazz, args)); } else if (candidateNames.length > 1) { Map candidates = new LinkedHashMap<>(candidateNames.length); @@ -1022,18 +1090,20 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto candidates.put(beanName, getType(beanName)); } } - String candidateName = determinePrimaryCandidate(candidates, requiredType); + String candidateName = determinePrimaryCandidate(candidates, clazz); if (candidateName == null) { - candidateName = determineHighestPriorityCandidate(candidates, requiredType); + candidateName = determineHighestPriorityCandidate(candidates, clazz); } if (candidateName != null) { Object beanInstance = candidates.get(candidateName); if (beanInstance == null || beanInstance instanceof Class) { - beanInstance = getBean(candidateName, requiredType, args); + beanInstance = getBean(candidateName, clazz, args); } return new NamedBeanHolder<>(candidateName, (T) beanInstance); } - throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet()); + if (!nonUniqueAsNull) { + throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet()); + } } return null; @@ -1110,7 +1180,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor); if (autowiredBeanName == null) { if (isRequired(descriptor) || !indicatesMultipleBeans(type)) { - return descriptor.resolveNotUnique(type, matchingBeans); + return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans); } else { // In case of an optional Collection/Map, silently ignore a non-unique case: @@ -1156,7 +1226,16 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) { Class type = descriptor.getDependencyType(); - if (type.isArray()) { + + if (descriptor.isStreamAccess()) { + Map matchingBeans = findAutowireCandidates(beanName, type, + new MultiElementDescriptor(descriptor)); + if (autowiredBeanNames != null) { + autowiredBeanNames.addAll(matchingBeans.keySet()); + } + return matchingBeans.values().stream(); + } + else if (type.isArray()) { Class componentType = type.getComponentType(); ResolvableType resolvableType = descriptor.getResolvableType(); Class resolvedArrayType = resolvableType.resolve(); @@ -1645,10 +1724,14 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } + private interface BeanObjectProvider extends ObjectProvider, Serializable { + } + + /** * Serializable ObjectFactory/ObjectProvider for lazy resolution of a dependency. */ - private class DependencyObjectProvider implements ObjectProvider, Serializable { + private class DependencyObjectProvider implements BeanObjectProvider { private final DependencyDescriptor descriptor; @@ -1724,7 +1807,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } @Override @Nullable - public Object resolveNotUnique(Class type, Map matchingBeans) { + public Object resolveNotUnique(ResolvableType type, Map matchingBeans) { return null; } }; @@ -1745,6 +1828,27 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto return doResolveDependency(this.descriptor, this.beanName, null, null); } } + + @SuppressWarnings("unchecked") + @Override + public Stream stream() { + DependencyDescriptor descriptorToUse = new DependencyDescriptor(this.descriptor) { + @Override + public boolean isStreamAccess() { + return true; + } + }; + Object result = doResolveDependency(descriptorToUse, this.beanName, null, null); + if (result instanceof Stream) { + return (Stream) result; + } + else if (result instanceof Collection) { + return ((Collection) result).stream(); + } + else { + return (result != null ? Stream.of(result) : Stream.empty()); + } + } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java index a45d24a5bd..9162692889 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java @@ -18,9 +18,11 @@ package org.springframework.beans.factory.support; import java.lang.annotation.Annotation; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanCreationException; @@ -31,6 +33,7 @@ import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.SmartFactoryBean; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; @@ -178,6 +181,73 @@ public class StaticListableBeanFactory implements ListableBeanFactory { return getBean(requiredType); } + @Override + public ObjectProvider getBeanProvider(Class requiredType) throws BeansException { + return getBeanProvider(ResolvableType.forRawClass(requiredType)); + } + + @SuppressWarnings("unchecked") + @Override + public ObjectProvider getBeanProvider(ResolvableType requiredType) { + return new ObjectProvider() { + @Override + public T getObject() throws BeansException { + String[] beanNames = getBeanNamesForType(requiredType); + if (beanNames.length == 1) { + return (T) getBean(beanNames[0], requiredType); + } + else if (beanNames.length > 1) { + throw new NoUniqueBeanDefinitionException(requiredType, beanNames); + } + else { + throw new NoSuchBeanDefinitionException(requiredType); + } + } + @Override + public T getObject(Object... args) throws BeansException { + String[] beanNames = getBeanNamesForType(requiredType); + if (beanNames.length == 1) { + return (T) getBean(beanNames[0], args); + } + else if (beanNames.length > 1) { + throw new NoUniqueBeanDefinitionException(requiredType, beanNames); + } + else { + throw new NoSuchBeanDefinitionException(requiredType); + } + } + @Override + @Nullable + public T getIfAvailable() throws BeansException { + String[] beanNames = getBeanNamesForType(requiredType); + if (beanNames.length == 1) { + return (T) getBean(beanNames[0]); + } + else if (beanNames.length > 1) { + throw new NoUniqueBeanDefinitionException(requiredType, beanNames); + } + else { + return null; + } + } + @Override + @Nullable + public T getIfUnique() throws BeansException { + String[] beanNames = getBeanNamesForType(requiredType); + if (beanNames.length == 1) { + return (T) getBean(beanNames[0]); + } + else { + return null; + } + } + @Override + public Stream stream() { + return Arrays.stream(getBeanNamesForType(requiredType)).map(name -> (T) getBean(name)); + } + }; + } + @Override public boolean containsBean(String name) { return this.beans.containsKey(name); 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 a0b0fbb02b..f8ded083ef 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 @@ -27,14 +27,17 @@ import java.security.PrivilegedAction; import java.text.NumberFormat; import java.text.ParseException; import java.util.Arrays; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Properties; import java.util.Set; import java.util.concurrent.Callable; +import java.util.stream.Collectors; import javax.annotation.Priority; import javax.security.auth.Subject; @@ -95,6 +98,7 @@ import org.springframework.tests.sample.beans.NestedTestBean; import org.springframework.tests.sample.beans.SideEffectBean; import org.springframework.tests.sample.beans.TestBean; import org.springframework.tests.sample.beans.factory.DummyFactory; +import org.springframework.util.SerializationTestUtils; import org.springframework.util.StopWatch; import org.springframework.util.StringValueResolver; @@ -1559,10 +1563,42 @@ public class DefaultListableBeanFactoryTests { } } - @Test(expected = NoSuchBeanDefinitionException.class) + @Test public void testGetBeanByTypeInstanceWithNoneFound() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); - lbf.getBean(ConstructorDependency.class, 42); + + try { + lbf.getBean(ConstructorDependency.class); + fail("Should have thrown NoSuchBeanDefinitionException"); + } + catch (NoSuchBeanDefinitionException ex) { + // expected + } + try { + lbf.getBean(ConstructorDependency.class, 42); + fail("Should have thrown NoSuchBeanDefinitionException"); + } + catch (NoSuchBeanDefinitionException ex) { + // expected + } + + ObjectProvider provider = lbf.getBeanProvider(ConstructorDependency.class); + try { + provider.getObject(); + fail("Should have thrown NoSuchBeanDefinitionException"); + } + catch (NoSuchBeanDefinitionException ex) { + // expected + } + try { + provider.getObject(42); + fail("Should have thrown NoSuchBeanDefinitionException"); + } + catch (NoSuchBeanDefinitionException ex) { + // expected + } + assertNull(provider.getIfAvailable()); + assertNull(provider.getIfUnique()); } @Test @@ -1571,9 +1607,27 @@ public class DefaultListableBeanFactoryTests { RootBeanDefinition bd1 = createConstructorDependencyBeanDefinition(99); parent.registerBeanDefinition("bd1", bd1); DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(parent); - ConstructorDependency bean = lbf.getBean(ConstructorDependency.class, 42); + + ConstructorDependency bean = lbf.getBean(ConstructorDependency.class); + assertThat(bean.beanName, equalTo("bd1")); + assertThat(bean.spouseAge, equalTo(99)); + bean = lbf.getBean(ConstructorDependency.class, 42); assertThat(bean.beanName, equalTo("bd1")); assertThat(bean.spouseAge, equalTo(42)); + + ObjectProvider provider = lbf.getBeanProvider(ConstructorDependency.class); + bean = provider.getObject(); + assertThat(bean.beanName, equalTo("bd1")); + assertThat(bean.spouseAge, equalTo(99)); + bean = provider.getObject(42); + assertThat(bean.beanName, equalTo("bd1")); + assertThat(bean.spouseAge, equalTo(42)); + bean = provider.getIfAvailable(); + assertThat(bean.beanName, equalTo("bd1")); + assertThat(bean.spouseAge, equalTo(99)); + bean = provider.getIfUnique(); + assertThat(bean.beanName, equalTo("bd1")); + assertThat(bean.spouseAge, equalTo(99)); } @Test @@ -1583,12 +1637,66 @@ public class DefaultListableBeanFactoryTests { RootBeanDefinition bd2 = new RootBeanDefinition(ConstructorDependency.class); bd2.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); bd2.getConstructorArgumentValues().addGenericArgumentValue("43"); - lbf.registerBeanDefinition("bd1", bd1); lbf.registerBeanDefinition("bd2", bd2); - thrown.expect(NoUniqueBeanDefinitionException.class); - lbf.getBean(ConstructorDependency.class, 42); + try { + lbf.getBean(ConstructorDependency.class); + fail("Should have thrown NoUniqueBeanDefinitionException"); + } + catch (NoUniqueBeanDefinitionException ex) { + // expected + } + try { + lbf.getBean(ConstructorDependency.class, 42); + fail("Should have thrown NoUniqueBeanDefinitionException"); + } + catch (NoUniqueBeanDefinitionException ex) { + // expected + } + + ObjectProvider provider = lbf.getBeanProvider(ConstructorDependency.class); + try { + provider.getObject(); + fail("Should have thrown NoUniqueBeanDefinitionException"); + } + catch (NoUniqueBeanDefinitionException ex) { + // expected + } + try { + provider.getObject(42); + fail("Should have thrown NoUniqueBeanDefinitionException"); + } + catch (NoUniqueBeanDefinitionException ex) { + // expected + } + try { + provider.getIfAvailable(); + fail("Should have thrown NoUniqueBeanDefinitionException"); + } + catch (NoUniqueBeanDefinitionException ex) { + // expected + } + assertNull(provider.getIfUnique()); + + Set resolved = new HashSet<>(); + for (ConstructorDependency instance : provider) { + resolved.add(instance); + } + assertEquals(2, resolved.size()); + assertTrue(resolved.contains(lbf.getBean("bd1"))); + assertTrue(resolved.contains(lbf.getBean("bd2"))); + + resolved = new HashSet<>(); + provider.forEach(resolved::add); + assertEquals(2, resolved.size()); + assertTrue(resolved.contains(lbf.getBean("bd1"))); + assertTrue(resolved.contains(lbf.getBean("bd2"))); + + resolved = provider.stream().collect(Collectors.toSet()); + assertEquals(2, resolved.size()); + assertTrue(resolved.contains(lbf.getBean("bd1"))); + assertTrue(resolved.contains(lbf.getBean("bd2"))); } @Test @@ -1599,9 +1707,46 @@ public class DefaultListableBeanFactoryTests { bd2.setPrimary(true); lbf.registerBeanDefinition("bd1", bd1); lbf.registerBeanDefinition("bd2", bd2); - ConstructorDependency bean = lbf.getBean(ConstructorDependency.class, 42); + + ConstructorDependency bean = lbf.getBean(ConstructorDependency.class); + assertThat(bean.beanName, equalTo("bd2")); + assertThat(bean.spouseAge, equalTo(43)); + bean = lbf.getBean(ConstructorDependency.class, 42); assertThat(bean.beanName, equalTo("bd2")); assertThat(bean.spouseAge, equalTo(42)); + + ObjectProvider provider = lbf.getBeanProvider(ConstructorDependency.class); + bean = provider.getObject(); + assertThat(bean.beanName, equalTo("bd2")); + assertThat(bean.spouseAge, equalTo(43)); + bean = provider.getObject(42); + assertThat(bean.beanName, equalTo("bd2")); + assertThat(bean.spouseAge, equalTo(42)); + bean = provider.getIfAvailable(); + assertThat(bean.beanName, equalTo("bd2")); + assertThat(bean.spouseAge, equalTo(43)); + bean = provider.getIfUnique(); + assertThat(bean.beanName, equalTo("bd2")); + assertThat(bean.spouseAge, equalTo(43)); + + Set resolved = new HashSet<>(); + for (ConstructorDependency instance : provider) { + resolved.add(instance); + } + assertEquals(2, resolved.size()); + assertTrue(resolved.contains(lbf.getBean("bd1"))); + assertTrue(resolved.contains(lbf.getBean("bd2"))); + + resolved = new HashSet<>(); + provider.forEach(resolved::add); + assertEquals(2, resolved.size()); + assertTrue(resolved.contains(lbf.getBean("bd1"))); + assertTrue(resolved.contains(lbf.getBean("bd2"))); + + resolved = provider.stream().collect(Collectors.toSet()); + assertEquals(2, resolved.size()); + assertTrue(resolved.contains(lbf.getBean("bd1"))); + assertTrue(resolved.contains(lbf.getBean("bd2"))); } @Test @@ -1611,9 +1756,9 @@ public class DefaultListableBeanFactoryTests { RootBeanDefinition bd2 = createConstructorDependencyBeanDefinition(43); bd1.setPrimary(true); bd2.setPrimary(true); - lbf.registerBeanDefinition("bd1", bd1); lbf.registerBeanDefinition("bd2", bd2); + thrown.expect(NoUniqueBeanDefinitionException.class); thrown.expectMessage(containsString("more than one 'primary'")); lbf.getBean(ConstructorDependency.class, 42); @@ -1642,6 +1787,31 @@ public class DefaultListableBeanFactoryTests { } } + @Test + public void testBeanProviderSerialization() throws Exception { + DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); + lbf.setSerializationId("test"); + + ObjectProvider provider = lbf.getBeanProvider(ConstructorDependency.class); + ObjectProvider deserialized = (ObjectProvider) SerializationTestUtils.serializeAndDeserialize(provider); + try { + deserialized.getObject(); + fail("Should have thrown NoSuchBeanDefinitionException"); + } + catch (NoSuchBeanDefinitionException ex) { + // expected + } + try { + deserialized.getObject(42); + fail("Should have thrown NoSuchBeanDefinitionException"); + } + catch (NoSuchBeanDefinitionException ex) { + // expected + } + assertNull(deserialized.getIfAvailable()); + assertNull(deserialized.getIfUnique()); + } + @Test public void testGetBeanWithArgsNotCreatedForFactoryBeanChecking() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); @@ -2859,6 +3029,21 @@ public class DefaultListableBeanFactoryTests { public void setBeanName(String name) { this.beanName = name; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ConstructorDependency that = (ConstructorDependency) o; + return spouseAge == that.spouseAge && + Objects.equals(spouse, that.spouse) && + Objects.equals(beanName, that.beanName); + } + + @Override + public int hashCode() { + return Objects.hash(spouse, spouseAge, beanName); + } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index 5bc2dc1289..b6218143e0 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -35,6 +35,7 @@ import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.Callable; +import java.util.stream.Collectors; import org.junit.Ignore; import org.junit.Test; @@ -811,13 +812,13 @@ public class AutowiredAnnotationBeanPostProcessorTests { } @Test - public void testSingleConstructorInjectionWithMultipleCandidatesAsOrderedCollection() { + public void testSingleConstructorInjectionWithMultipleCandidatesAsRequiredCollection() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); - bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SingleConstructorCollectionInjectionBean.class)); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SingleConstructorRequiredCollectionBean.class)); TestBean tb = new TestBean(); bf.registerSingleton("testBean", tb); FixedOrder2NestedTestBean ntb1 = new FixedOrder2NestedTestBean(); @@ -825,7 +826,7 @@ public class AutowiredAnnotationBeanPostProcessorTests { FixedOrder1NestedTestBean ntb2 = new FixedOrder1NestedTestBean(); bf.registerSingleton("nestedTestBean2", ntb2); - SingleConstructorCollectionInjectionBean bean = (SingleConstructorCollectionInjectionBean) bf.getBean("annotatedBean"); + SingleConstructorRequiredCollectionBean bean = (SingleConstructorRequiredCollectionBean) bf.getBean("annotatedBean"); assertSame(tb, bean.getTestBean()); assertEquals(2, bean.getNestedTestBeans().size()); assertSame(ntb2, bean.getNestedTestBeans().get(0)); @@ -840,11 +841,52 @@ public class AutowiredAnnotationBeanPostProcessorTests { AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); - bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SingleConstructorCollectionInjectionBean.class)); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SingleConstructorRequiredCollectionBean.class)); TestBean tb = new TestBean(); bf.registerSingleton("testBean", tb); - SingleConstructorCollectionInjectionBean bean = (SingleConstructorCollectionInjectionBean) bf.getBean("annotatedBean"); + SingleConstructorRequiredCollectionBean bean = (SingleConstructorRequiredCollectionBean) bf.getBean("annotatedBean"); + assertSame(tb, bean.getTestBean()); + assertNotNull(bean.getNestedTestBeans()); + assertTrue(bean.getNestedTestBeans().isEmpty()); + bf.destroySingletons(); + } + + @Test + public void testSingleConstructorInjectionWithMultipleCandidatesAsOrderedCollection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SingleConstructorOptionalCollectionBean.class)); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + FixedOrder2NestedTestBean ntb1 = new FixedOrder2NestedTestBean(); + bf.registerSingleton("nestedTestBean1", ntb1); + FixedOrder1NestedTestBean ntb2 = new FixedOrder1NestedTestBean(); + bf.registerSingleton("nestedTestBean2", ntb2); + + SingleConstructorOptionalCollectionBean bean = (SingleConstructorOptionalCollectionBean) bf.getBean("annotatedBean"); + assertSame(tb, bean.getTestBean()); + assertEquals(2, bean.getNestedTestBeans().size()); + assertSame(ntb2, bean.getNestedTestBeans().get(0)); + assertSame(ntb1, bean.getNestedTestBeans().get(1)); + bf.destroySingletons(); + } + + @Test + public void testSingleConstructorInjectionWithEmptyCollectionAsNull() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SingleConstructorOptionalCollectionBean.class)); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + + SingleConstructorOptionalCollectionBean bean = (SingleConstructorOptionalCollectionBean) bf.getBean("annotatedBean"); assertSame(tb, bean.getTestBean()); assertNull(bean.getNestedTestBeans()); bf.destroySingletons(); @@ -857,7 +899,7 @@ public class AutowiredAnnotationBeanPostProcessorTests { AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); - bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SingleConstructorCollectionInjectionBean.class)); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SingleConstructorOptionalCollectionBean.class)); bf.getBean("annotatedBean"); } @@ -869,7 +911,7 @@ public class AutowiredAnnotationBeanPostProcessorTests { AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); - bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SingleConstructorCollectionInjectionBean.class)); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SingleConstructorOptionalCollectionBean.class)); RootBeanDefinition tb = new RootBeanDefinition(NullFactoryMethods.class); tb.setFactoryMethodName("createTestBean"); bf.registerBeanDefinition("testBean", tb); @@ -1333,17 +1375,17 @@ public class AutowiredAnnotationBeanPostProcessorTests { } @Test - public void testSmartObjectFactoryInjectionWithPrototype() { + public void testObjectProviderInjectionWithPrototype() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); - bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SmartObjectFactoryInjectionBean.class)); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectProviderInjectionBean.class)); RootBeanDefinition tbd = new RootBeanDefinition(TestBean.class); tbd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); bf.registerBeanDefinition("testBean", tbd); - SmartObjectFactoryInjectionBean bean = (SmartObjectFactoryInjectionBean) bf.getBean("annotatedBean"); + ObjectProviderInjectionBean bean = (ObjectProviderInjectionBean) bf.getBean("annotatedBean"); assertEquals(bf.getBean("testBean"), bean.getTestBean()); assertEquals(bf.getBean("testBean", "myName"), bean.getTestBean("myName")); assertEquals(bf.getBean("testBean"), bean.getOptionalTestBean()); @@ -1352,19 +1394,30 @@ public class AutowiredAnnotationBeanPostProcessorTests { assertEquals(bf.getBean("testBean"), bean.getUniqueTestBean()); assertEquals(bf.getBean("testBean"), bean.getUniqueTestBeanWithDefault()); assertEquals(bf.getBean("testBean"), bean.consumeUniqueTestBean()); + + List testBeans = bean.iterateTestBeans(); + assertEquals(1, testBeans.size()); + assertTrue(testBeans.contains(bf.getBean("testBean"))); + testBeans = bean.forEachTestBeans(); + assertEquals(1, testBeans.size()); + assertTrue(testBeans.contains(bf.getBean("testBean"))); + testBeans = bean.streamTestBeans(); + assertEquals(1, testBeans.size()); + assertTrue(testBeans.contains(bf.getBean("testBean"))); + bf.destroySingletons(); } @Test - public void testSmartObjectFactoryInjectionWithSingletonTarget() { + public void testObjectProviderInjectionWithSingletonTarget() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); - bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SmartObjectFactoryInjectionBean.class)); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectProviderInjectionBean.class)); bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); - SmartObjectFactoryInjectionBean bean = (SmartObjectFactoryInjectionBean) bf.getBean("annotatedBean"); + ObjectProviderInjectionBean bean = (ObjectProviderInjectionBean) bf.getBean("annotatedBean"); assertSame(bf.getBean("testBean"), bean.getTestBean()); assertSame(bf.getBean("testBean"), bean.getOptionalTestBean()); assertSame(bf.getBean("testBean"), bean.getOptionalTestBeanWithDefault()); @@ -1372,18 +1425,29 @@ public class AutowiredAnnotationBeanPostProcessorTests { assertSame(bf.getBean("testBean"), bean.getUniqueTestBean()); assertSame(bf.getBean("testBean"), bean.getUniqueTestBeanWithDefault()); assertEquals(bf.getBean("testBean"), bean.consumeUniqueTestBean()); + + List testBeans = bean.iterateTestBeans(); + assertEquals(1, testBeans.size()); + assertTrue(testBeans.contains(bf.getBean("testBean"))); + testBeans = bean.forEachTestBeans(); + assertEquals(1, testBeans.size()); + assertTrue(testBeans.contains(bf.getBean("testBean"))); + testBeans = bean.streamTestBeans(); + assertEquals(1, testBeans.size()); + assertTrue(testBeans.contains(bf.getBean("testBean"))); + bf.destroySingletons(); } @Test - public void testSmartObjectFactoryInjectionWithTargetNotAvailable() { + public void testObjectProviderInjectionWithTargetNotAvailable() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); - bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SmartObjectFactoryInjectionBean.class)); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectProviderInjectionBean.class)); - SmartObjectFactoryInjectionBean bean = (SmartObjectFactoryInjectionBean) bf.getBean("annotatedBean"); + ObjectProviderInjectionBean bean = (ObjectProviderInjectionBean) bf.getBean("annotatedBean"); try { bean.getTestBean(); fail("Should have thrown NoSuchBeanDefinitionException"); @@ -1397,20 +1461,28 @@ public class AutowiredAnnotationBeanPostProcessorTests { assertEquals(new TestBean("default"), bean.getUniqueTestBeanWithDefault()); assertNull(bean.getUniqueTestBean()); assertNull(bean.consumeUniqueTestBean()); + + List testBeans = bean.iterateTestBeans(); + assertTrue(testBeans.isEmpty()); + testBeans = bean.forEachTestBeans(); + assertTrue(testBeans.isEmpty()); + testBeans = bean.streamTestBeans(); + assertTrue(testBeans.isEmpty()); + bf.destroySingletons(); } @Test - public void testSmartObjectFactoryInjectionWithTargetNotUnique() { + public void testObjectProviderInjectionWithTargetNotUnique() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); - bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SmartObjectFactoryInjectionBean.class)); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectProviderInjectionBean.class)); bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class)); bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class)); - SmartObjectFactoryInjectionBean bean = (SmartObjectFactoryInjectionBean) bf.getBean("annotatedBean"); + ObjectProviderInjectionBean bean = (ObjectProviderInjectionBean) bf.getBean("annotatedBean"); try { bean.getTestBean(); fail("Should have thrown NoUniqueBeanDefinitionException"); @@ -1434,16 +1506,30 @@ public class AutowiredAnnotationBeanPostProcessorTests { } assertNull(bean.getUniqueTestBean()); assertNull(bean.consumeUniqueTestBean()); + + List testBeans = bean.iterateTestBeans(); + assertEquals(2, testBeans.size()); + assertTrue(testBeans.contains(bf.getBean("testBean1"))); + assertTrue(testBeans.contains(bf.getBean("testBean2"))); + testBeans = bean.forEachTestBeans(); + assertEquals(2, testBeans.size()); + assertTrue(testBeans.contains(bf.getBean("testBean1"))); + assertTrue(testBeans.contains(bf.getBean("testBean2"))); + testBeans = bean.streamTestBeans(); + assertEquals(2, testBeans.size()); + assertTrue(testBeans.contains(bf.getBean("testBean1"))); + assertTrue(testBeans.contains(bf.getBean("testBean2"))); + bf.destroySingletons(); } @Test - public void testSmartObjectFactoryInjectionWithTargetPrimary() { + public void testObjectProviderInjectionWithTargetPrimary() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); - bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SmartObjectFactoryInjectionBean.class)); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectProviderInjectionBean.class)); RootBeanDefinition tb1 = new RootBeanDefinition(TestBean.class); tb1.setPrimary(true); bf.registerBeanDefinition("testBean1", tb1); @@ -1451,13 +1537,27 @@ public class AutowiredAnnotationBeanPostProcessorTests { tb2.setLazyInit(true); bf.registerBeanDefinition("testBean2", tb2); - SmartObjectFactoryInjectionBean bean = (SmartObjectFactoryInjectionBean) bf.getBean("annotatedBean"); + ObjectProviderInjectionBean bean = (ObjectProviderInjectionBean) bf.getBean("annotatedBean"); assertSame(bf.getBean("testBean1"), bean.getTestBean()); assertSame(bf.getBean("testBean1"), bean.getOptionalTestBean()); assertSame(bf.getBean("testBean1"), bean.consumeOptionalTestBean()); assertSame(bf.getBean("testBean1"), bean.getUniqueTestBean()); assertSame(bf.getBean("testBean1"), bean.consumeUniqueTestBean()); assertFalse(bf.containsSingleton("testBean2")); + + List testBeans = bean.iterateTestBeans(); + assertEquals(2, testBeans.size()); + assertTrue(testBeans.contains(bf.getBean("testBean1"))); + assertTrue(testBeans.contains(bf.getBean("testBean2"))); + testBeans = bean.forEachTestBeans(); + assertEquals(2, testBeans.size()); + assertTrue(testBeans.contains(bf.getBean("testBean1"))); + assertTrue(testBeans.contains(bf.getBean("testBean2"))); + testBeans = bean.streamTestBeans(); + assertEquals(2, testBeans.size()); + assertTrue(testBeans.contains(bf.getBean("testBean1"))); + assertTrue(testBeans.contains(bf.getBean("testBean2"))); + bf.destroySingletons(); } @@ -2941,13 +3041,34 @@ public class AutowiredAnnotationBeanPostProcessorTests { } - public static class SingleConstructorCollectionInjectionBean { + public static class SingleConstructorRequiredCollectionBean { private ITestBean testBean; private List nestedTestBeans; - public SingleConstructorCollectionInjectionBean(ITestBean testBean, + public SingleConstructorRequiredCollectionBean(ITestBean testBean, List nestedTestBeans) { + this.testBean = testBean; + this.nestedTestBeans = nestedTestBeans; + } + + public ITestBean getTestBean() { + return this.testBean; + } + + public List getNestedTestBeans() { + return this.nestedTestBeans; + } + } + + + public static class SingleConstructorOptionalCollectionBean { + + private ITestBean testBean; + + private List nestedTestBeans; + + public SingleConstructorOptionalCollectionBean(ITestBean testBean, @Autowired(required = false) List nestedTestBeans) { this.testBean = testBean; this.nestedTestBeans = nestedTestBeans; @@ -3096,46 +3217,64 @@ public class AutowiredAnnotationBeanPostProcessorTests { } - public static class SmartObjectFactoryInjectionBean { + public static class ObjectProviderInjectionBean { @Autowired - private ObjectProvider testBeanFactory; + private ObjectProvider testBeanProvider; private TestBean consumedTestBean; public TestBean getTestBean() { - return this.testBeanFactory.getObject(); + return this.testBeanProvider.getObject(); } public TestBean getTestBean(String name) { - return this.testBeanFactory.getObject(name); + return this.testBeanProvider.getObject(name); } public TestBean getOptionalTestBean() { - return this.testBeanFactory.getIfAvailable(); + return this.testBeanProvider.getIfAvailable(); } public TestBean getOptionalTestBeanWithDefault() { - return this.testBeanFactory.getIfAvailable(() -> new TestBean("default")); + return this.testBeanProvider.getIfAvailable(() -> new TestBean("default")); } public TestBean consumeOptionalTestBean() { - this.testBeanFactory.ifAvailable(tb -> consumedTestBean = tb); + this.testBeanProvider.ifAvailable(tb -> consumedTestBean = tb); return consumedTestBean; } public TestBean getUniqueTestBean() { - return this.testBeanFactory.getIfUnique(); + return this.testBeanProvider.getIfUnique(); } public TestBean getUniqueTestBeanWithDefault() { - return this.testBeanFactory.getIfUnique(() -> new TestBean("default")); + return this.testBeanProvider.getIfUnique(() -> new TestBean("default")); } public TestBean consumeUniqueTestBean() { - this.testBeanFactory.ifUnique(tb -> consumedTestBean = tb); + this.testBeanProvider.ifUnique(tb -> consumedTestBean = tb); return consumedTestBean; } + + public List iterateTestBeans() { + List resolved = new LinkedList<>(); + for (TestBean tb : this.testBeanProvider) { + resolved.add(tb); + } + return resolved; + } + + public List forEachTestBeans() { + List resolved = new LinkedList<>(); + this.testBeanProvider.forEach(resolved::add); + return resolved; + } + + public List streamTestBeans() { + return this.testBeanProvider.stream().collect(Collectors.toList()); + } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java index 4a0fcec071..bdb9854119 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java @@ -30,6 +30,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.junit.Test; import org.mockito.Mockito; @@ -37,6 +38,8 @@ import org.mockito.Mockito; import org.springframework.beans.PropertyEditorRegistrar; import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.beans.propertyeditors.CustomNumberEditor; @@ -160,7 +163,7 @@ public class BeanFactoryGenericsTests { } @Test - public void testGenericListOfArraysProperty() throws MalformedURLException { + public void testGenericListOfArraysProperty() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); @@ -863,6 +866,66 @@ public class BeanFactoryGenericsTests { assertEquals("store1", doubleStoreNames[0]); assertEquals(1, floatStoreNames.length); assertEquals("store2", floatStoreNames[0]); + + ObjectProvider> numberStoreProvider = bf.getBeanProvider(ResolvableType.forClass(NumberStore.class)); + ObjectProvider> doubleStoreProvider = bf.getBeanProvider(ResolvableType.forClassWithGenerics(NumberStore.class, Double.class)); + ObjectProvider> floatStoreProvider = bf.getBeanProvider(ResolvableType.forClassWithGenerics(NumberStore.class, Float.class)); + try { + numberStoreProvider.getObject(); + fail("Should have thrown NoUniqueBeanDefinitionException"); + } + catch (NoUniqueBeanDefinitionException ex) { + // expected + } + try { + numberStoreProvider.getIfAvailable(); + fail("Should have thrown NoUniqueBeanDefinitionException"); + } + catch (NoUniqueBeanDefinitionException ex) { + // expected + } + assertNull(numberStoreProvider.getIfUnique()); + assertSame(bf.getBean("store1"), doubleStoreProvider.getObject()); + assertSame(bf.getBean("store1"), doubleStoreProvider.getIfAvailable()); + assertSame(bf.getBean("store1"), doubleStoreProvider.getIfUnique()); + assertSame(bf.getBean("store2"), floatStoreProvider.getObject()); + assertSame(bf.getBean("store2"), floatStoreProvider.getIfAvailable()); + assertSame(bf.getBean("store2"), floatStoreProvider.getIfUnique()); + + Set resolved = new HashSet<>(); + for (NumberStore instance : numberStoreProvider) { + resolved.add(instance); + } + assertEquals(2, resolved.size()); + assertTrue(resolved.contains(bf.getBean("store1"))); + assertTrue(resolved.contains(bf.getBean("store2"))); + + resolved = numberStoreProvider.stream().collect(Collectors.toSet()); + assertEquals(2, resolved.size()); + assertTrue(resolved.contains(bf.getBean("store1"))); + assertTrue(resolved.contains(bf.getBean("store2"))); + + resolved = new HashSet<>(); + for (NumberStore instance : doubleStoreProvider) { + resolved.add(instance); + } + assertEquals(1, resolved.size()); + assertTrue(resolved.contains(bf.getBean("store1"))); + + resolved = doubleStoreProvider.stream().collect(Collectors.toSet()); + assertEquals(1, resolved.size()); + assertTrue(resolved.contains(bf.getBean("store1"))); + + resolved = new HashSet<>(); + for (NumberStore instance : floatStoreProvider) { + resolved.add(instance); + } + assertEquals(1, resolved.size()); + assertTrue(resolved.contains(bf.getBean("store2"))); + + resolved = floatStoreProvider.stream().collect(Collectors.toSet()); + assertEquals(1, resolved.size()); + assertTrue(resolved.contains(bf.getBean("store2"))); } diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index e5d2ebb158..ba60fa82e6 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -35,6 +35,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.CachedIntrospectionResults; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -1083,7 +1084,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader } @Override - public T getBean(String name, @Nullable Class requiredType) throws BeansException { + public T getBean(String name, Class requiredType) throws BeansException { assertBeanFactoryActive(); return getBeanFactory().getBean(name, requiredType); } @@ -1106,6 +1107,18 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader return getBeanFactory().getBean(requiredType, args); } + @Override + public ObjectProvider getBeanProvider(Class requiredType) { + assertBeanFactoryActive(); + return getBeanFactory().getBeanProvider(requiredType); + } + + @Override + public ObjectProvider getBeanProvider(ResolvableType requiredType) { + assertBeanFactoryActive(); + return getBeanFactory().getBeanProvider(requiredType); + } + @Override public boolean containsBean(String name) { return getBeanFactory().containsBean(name); @@ -1130,7 +1143,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader } @Override - public boolean isTypeMatch(String name, @Nullable Class typeToMatch) throws NoSuchBeanDefinitionException { + public boolean isTypeMatch(String name, Class typeToMatch) throws NoSuchBeanDefinitionException { assertBeanFactoryActive(); return getBeanFactory().isTypeMatch(name, typeToMatch); } diff --git a/spring-context/src/main/java/org/springframework/jndi/support/SimpleJndiBeanFactory.java b/spring-context/src/main/java/org/springframework/jndi/support/SimpleJndiBeanFactory.java index 964584f8c9..62a2623668 100644 --- a/spring-context/src/main/java/org/springframework/jndi/support/SimpleJndiBeanFactory.java +++ b/spring-context/src/main/java/org/springframework/jndi/support/SimpleJndiBeanFactory.java @@ -29,6 +29,8 @@ import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanNotOfRequiredTypeException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.core.ResolvableType; import org.springframework.jndi.JndiLocatorSupport; import org.springframework.jndi.TypeMismatchNamingException; @@ -107,7 +109,7 @@ public class SimpleJndiBeanFactory extends JndiLocatorSupport implements BeanFac } @Override - public T getBean(String name, @Nullable Class requiredType) throws BeansException { + public T getBean(String name, Class requiredType) throws BeansException { try { if (isSingleton(name)) { return doGetSingleton(name, requiredType); @@ -150,6 +152,49 @@ public class SimpleJndiBeanFactory extends JndiLocatorSupport implements BeanFac return getBean(requiredType); } + @Override + public ObjectProvider getBeanProvider(Class requiredType) { + return new ObjectProvider() { + @Override + public T getObject() throws BeansException { + return getBean(requiredType); + } + @Override + public T getObject(Object... args) throws BeansException { + return getBean(requiredType, args); + } + @Override + @Nullable + public T getIfAvailable() throws BeansException { + try { + return getBean(requiredType); + } + catch (NoUniqueBeanDefinitionException ex) { + throw ex; + } + catch (NoSuchBeanDefinitionException ex) { + return null; + } + } + @Override + @Nullable + public T getIfUnique() throws BeansException { + try { + return getBean(requiredType); + } + catch (NoSuchBeanDefinitionException ex) { + return null; + } + } + }; + } + + @Override + public ObjectProvider getBeanProvider(ResolvableType requiredType) { + throw new UnsupportedOperationException( + "SimpleJndiBeanFactory does not support resolution by ResolvableType"); + } + @Override public boolean containsBean(String name) { if (this.singletonObjects.containsKey(name) || this.resourceTypes.containsKey(name)) { diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java index 3977236663..13d5efa091 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java @@ -44,12 +44,6 @@ import static org.springframework.util.StringUtils.*; */ public class AnnotationConfigApplicationContextTests { - @Test(expected = IllegalArgumentException.class) - public void nullGetBeanParameterIsDisallowed() { - ApplicationContext context = new AnnotationConfigApplicationContext(Config.class); - context.getBean((Class) null); - } - @Test public void scanAndRefresh() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java index 5aa861ae75..6d831b48fd 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java @@ -801,6 +801,40 @@ public class ConfigurationClassPostProcessorTests { assertSame(ctx.getBean(BarImpl.class), ctx.getBean(FooImpl.class).bar); } + @Test + public void testCollectionArgumentOnBeanMethod() { + ApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionArgumentConfiguration.class, TestBean.class); + CollectionArgumentConfiguration bean = ctx.getBean(CollectionArgumentConfiguration.class); + assertNotNull(bean.testBeans); + assertEquals(1, bean.testBeans.size()); + assertSame(ctx.getBean(TestBean.class), bean.testBeans.get(0)); + } + + @Test + public void testEmptyCollectionArgumentOnBeanMethod() { + ApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionArgumentConfiguration.class); + CollectionArgumentConfiguration bean = ctx.getBean(CollectionArgumentConfiguration.class); + assertNotNull(bean.testBeans); + assertTrue(bean.testBeans.isEmpty()); + } + + @Test + public void testMapArgumentOnBeanMethod() { + ApplicationContext ctx = new AnnotationConfigApplicationContext(MapArgumentConfiguration.class, DummyRunnable.class); + MapArgumentConfiguration bean = ctx.getBean(MapArgumentConfiguration.class); + assertNotNull(bean.testBeans); + assertEquals(1, bean.testBeans.size()); + assertSame(ctx.getBean(Runnable.class), bean.testBeans.values().iterator().next()); + } + + @Test + public void testEmptyMapArgumentOnBeanMethod() { + ApplicationContext ctx = new AnnotationConfigApplicationContext(MapArgumentConfiguration.class); + MapArgumentConfiguration bean = ctx.getBean(MapArgumentConfiguration.class); + assertNotNull(bean.testBeans); + assertTrue(bean.testBeans.isEmpty()); + } + @Test public void testCollectionInjectionFromSameConfigurationClass() { ApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionInjectionConfiguration.class); @@ -1564,6 +1598,43 @@ public class ConfigurationClassPostProcessorTests { } } + public static class DummyRunnable implements Runnable { + + @Override + public void run() { + /* no-op */ + } + } + + @Configuration + static class CollectionArgumentConfiguration { + + List testBeans; + + @Bean(autowireCandidate = false) + public TestBean thing(List testBeans) { + this.testBeans = testBeans; + return new TestBean(); + } + } + + @Configuration + public static class MapArgumentConfiguration { + + Map testBeans; + + @Bean(autowireCandidate = false) + Runnable testBean(Map testBeans) { + this.testBeans = testBeans; + return () -> {}; + } + + // Unrelated, not to be considered as a factory method + private boolean testBean(boolean param) { + return param; + } + } + @Configuration static class CollectionInjectionConfiguration { diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java index 43bcbc9809..99a575f17e 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -29,6 +29,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.TypeConverter; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.NamedBeanHolder; @@ -161,7 +162,7 @@ class StubWebApplicationContext implements WebApplicationContext { } @Override - public T getBean(String name, @Nullable Class requiredType) throws BeansException { + public T getBean(String name, Class requiredType) throws BeansException { return this.beanFactory.getBean(name, requiredType); } @@ -180,6 +181,16 @@ class StubWebApplicationContext implements WebApplicationContext { return this.beanFactory.getBean(requiredType, args); } + @Override + public ObjectProvider getBeanProvider(Class requiredType) { + return this.beanFactory.getBeanProvider(requiredType); + } + + @Override + public ObjectProvider getBeanProvider(ResolvableType requiredType) { + return this.beanFactory.getBeanProvider(requiredType); + } + @Override public boolean containsBean(String name) { return this.beanFactory.containsBean(name); @@ -201,7 +212,7 @@ class StubWebApplicationContext implements WebApplicationContext { } @Override - public boolean isTypeMatch(String name, @Nullable Class typeToMatch) throws NoSuchBeanDefinitionException { + public boolean isTypeMatch(String name, Class typeToMatch) throws NoSuchBeanDefinitionException { return this.beanFactory.isTypeMatch(name, typeToMatch); }