Link factoryMethod consistently in AOT-generated bean definitions

Closes gh-28748
This commit is contained in:
Stephane Nicoll 2022-07-18 09:20:26 +01:00 committed by Phillip Webb
parent f2d31b7a20
commit 75ab47b57c
9 changed files with 506 additions and 320 deletions

View File

@ -26,7 +26,7 @@ import org.springframework.util.ClassUtils;
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 6.0 * @since 6.0
* @see AutowiredInstantiationArgumentsResolver * @see BeanInstanceSupplier
* @see AutowiredMethodArgumentsResolver * @see AutowiredMethodArgumentsResolver
*/ */
@FunctionalInterface @FunctionalInterface

View File

@ -105,7 +105,7 @@ public final class AutowiredMethodArgumentsResolver extends AutowiredElementReso
} }
/** /**
* Return a new {@link AutowiredInstantiationArgumentsResolver} instance * Return a new {@link AutowiredMethodArgumentsResolver} instance
* that uses direct bean name injection shortcuts for specific parameters. * that uses direct bean name injection shortcuts for specific parameters.
* @param beanNames the bean names to use as shortcuts (aligned with the * @param beanNames the bean names to use as shortcuts (aligned with the
* method parameters) * method parameters)

View File

@ -40,6 +40,7 @@ import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueH
import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory; import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionValueResolver; import org.springframework.beans.factory.support.BeanDefinitionValueResolver;
import org.springframework.beans.factory.support.InstanceSupplier;
import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.CollectionFactory; import org.springframework.core.CollectionFactory;
@ -49,67 +50,81 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.util.function.ThrowingBiFunction;
import org.springframework.util.function.ThrowingFunction; import org.springframework.util.function.ThrowingFunction;
import org.springframework.util.function.ThrowingSupplier;
/** /**
* Resolver used to support the autowiring of constructors or factory methods. * Specialized {@link InstanceSupplier} that provides the factory {@link Method}
* Typically used in AOT-processed applications as a targeted alternative to the * used to instantiate the underlying bean instance, if any. Transparently
* reflection based injection. * handles resolution of {@link AutowiredArguments} if necessary. Typically used
* in AOT-processed applications as a targeted alternative to the reflection
* based injection.
* <p> * <p>
* When resolving arguments in a native image, the {@link Constructor} or * If no {@code generator} is provided, reflection is used to instantiate the
* {@link Method} being used must be marked with an * bean instance, and full {@link ExecutableMode#INVOKE invocation} hints are
* {@link ExecutableMode#INTROSPECT introspection} hint so that parameter * contributed. Multiple generator callback styles are supported:
* annotations can be read. Full {@link ExecutableMode#INVOKE invocation} hints * <ul>
* are only required if the {@code resolveAndInstantiate} methods of this class * <li>A function with the {@code registeredBean} and resolved {@code arguments}
* are being used (typically to support private constructors, methods or * for executables that require arguments resolution. An
* classes). * {@link ExecutableMode#INTROSPECT introspection} hint is added so that
* parameter annotations can be read </li>
* <li>A function with only the {@code registeredBean} for simpler cases that
* do not require resolution of arguments</li>
* <li>A supplier when a method reference can be used</li>
* </ul>
* Generator callbacks handle checked exceptions so that the caller does not
* have to deal with it.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 6.0 * @since 6.0
* @see AutowiredArguments * @see AutowiredArguments
*/ */
public final class AutowiredInstantiationArgumentsResolver extends AutowiredElementResolver { public final class BeanInstanceSupplier extends AutowiredElementResolver implements InstanceSupplier<Object> {
private final ExecutableLookup lookup; private final ExecutableLookup lookup;
@Nullable
private final ThrowingBiFunction<RegisteredBean, AutowiredArguments, Object> generator;
@Nullable @Nullable
private final String[] shortcuts; private final String[] shortcuts;
private AutowiredInstantiationArgumentsResolver(ExecutableLookup lookup, private BeanInstanceSupplier(ExecutableLookup lookup,
@Nullable ThrowingBiFunction<RegisteredBean, AutowiredArguments, Object> generator,
@Nullable String[] shortcuts) { @Nullable String[] shortcuts) {
this.lookup = lookup; this.lookup = lookup;
this.generator = generator;
this.shortcuts = shortcuts; this.shortcuts = shortcuts;
} }
/** /**
* Create a {@link AutowiredInstantiationArgumentsResolver} that resolves * Create a {@link BeanInstanceSupplier} that resolves
* arguments for the specified bean constructor. * arguments for the specified bean constructor.
* @param parameterTypes the constructor parameter types * @param parameterTypes the constructor parameter types
* @return a new {@link AutowiredInstantiationArgumentsResolver} instance * @return a new {@link BeanInstanceSupplier} instance
*/ */
public static AutowiredInstantiationArgumentsResolver forConstructor( public static BeanInstanceSupplier forConstructor(
Class<?>... parameterTypes) { Class<?>... parameterTypes) {
Assert.notNull(parameterTypes, "'parameterTypes' must not be null"); Assert.notNull(parameterTypes, "'parameterTypes' must not be null");
Assert.noNullElements(parameterTypes, Assert.noNullElements(parameterTypes,
"'parameterTypes' must not contain null elements"); "'parameterTypes' must not contain null elements");
return new AutowiredInstantiationArgumentsResolver( return new BeanInstanceSupplier(
new ConstructorLookup(parameterTypes), null); new ConstructorLookup(parameterTypes), null, null);
} }
/** /**
* Create a new {@link AutowiredInstantiationArgumentsResolver} that * Create a new {@link BeanInstanceSupplier} that
* resolves arguments for the specified factory method. * resolves arguments for the specified factory method.
* @param declaringClass the class that declares the factory method * @param declaringClass the class that declares the factory method
* @param methodName the factory method name * @param methodName the factory method name
* @param parameterTypes the factory method parameter types * @param parameterTypes the factory method parameter types
* @return a new {@link AutowiredInstantiationArgumentsResolver} instance * @return a new {@link BeanInstanceSupplier} instance
*/ */
public static AutowiredInstantiationArgumentsResolver forFactoryMethod( public static BeanInstanceSupplier forFactoryMethod(
Class<?> declaringClass, String methodName, Class<?>... parameterTypes) { Class<?> declaringClass, String methodName, Class<?>... parameterTypes) {
Assert.notNull(declaringClass, "'declaringClass' must not be null"); Assert.notNull(declaringClass, "'declaringClass' must not be null");
@ -117,9 +132,9 @@ public final class AutowiredInstantiationArgumentsResolver extends AutowiredElem
Assert.notNull(parameterTypes, "'parameterTypes' must not be null"); Assert.notNull(parameterTypes, "'parameterTypes' must not be null");
Assert.noNullElements(parameterTypes, Assert.noNullElements(parameterTypes,
"'parameterTypes' must not contain null elements"); "'parameterTypes' must not contain null elements");
return new AutowiredInstantiationArgumentsResolver( return new BeanInstanceSupplier(
new FactoryMethodLookup(declaringClass, methodName, parameterTypes), new FactoryMethodLookup(declaringClass, methodName, parameterTypes),
null); null, null);
} }
@ -128,32 +143,82 @@ public final class AutowiredInstantiationArgumentsResolver extends AutowiredElem
} }
/** /**
* Return a new {@link AutowiredInstantiationArgumentsResolver} instance * Return a new {@link BeanInstanceSupplier} instance that uses the specified
* that uses direct bean name injection shortcuts for specific parameters. * {@code generator} bi-function to instantiate the underlying bean.
* @param beanNames the bean names to use as shortcuts (aligned with the * @param generator a {@link ThrowingBiFunction} that uses the
* constructor or factory method parameters) * {@link RegisteredBean} and resolved {@link AutowiredArguments} to
* @return a new {@link AutowiredInstantiationArgumentsResolver} instance * instantiate the underlying bean
* that uses the shortcuts * @return a new {@link BeanInstanceSupplier} instance with the specified
* generator
*/ */
public AutowiredInstantiationArgumentsResolver withShortcuts(String... beanNames) { public BeanInstanceSupplier withGenerator(
return new AutowiredInstantiationArgumentsResolver(this.lookup, beanNames); ThrowingBiFunction<RegisteredBean, AutowiredArguments, Object> generator) {
Assert.notNull(generator, "'generator' must not be null");
return new BeanInstanceSupplier(this.lookup, generator, this.shortcuts);
} }
/** /**
* Resolve arguments for the specified registered bean and provide them to * Return a new {@link BeanInstanceSupplier} instance that uses the specified
* the given generator in order to return a result. * {@code generator} function to instantiate the underlying bean.
* @param registeredBean the registered bean * @param generator a {@link ThrowingFunction} that uses the
* @param generator the generator to execute with the resolved constructor * {@link RegisteredBean} to instantiate the underlying bean
* or factory method arguments * @return a new {@link BeanInstanceSupplier} instance with the specified
* generator
*/ */
public <T> T resolve(RegisteredBean registeredBean, public BeanInstanceSupplier withGenerator(
ThrowingFunction<AutowiredArguments, T> generator) { ThrowingFunction<RegisteredBean, Object> generator) {
Assert.notNull(generator, "'generator' must not be null");
return new BeanInstanceSupplier(this.lookup, (registeredBean, args) ->
generator.apply(registeredBean), this.shortcuts);
}
/**
* Return a new {@link BeanInstanceSupplier} instance that uses the specified
* {@code generator} supplier to instantiate the underlying bean.
* @param generator a {@link ThrowingSupplier} to instantiate the underlying
* bean
* @return a new {@link BeanInstanceSupplier} instance with the specified
* generator
*/
public BeanInstanceSupplier withGenerator(ThrowingSupplier<Object> generator) {
Assert.notNull(generator, "'generator' must not be null");
return new BeanInstanceSupplier(this.lookup, (registeredBean, args) ->
generator.get(), this.shortcuts);
}
/**
* Return a new {@link BeanInstanceSupplier} instance
* that uses direct bean name injection shortcuts for specific parameters.
* @param beanNames the bean names to use as shortcuts (aligned with the
* constructor or factory method parameters)
* @return a new {@link BeanInstanceSupplier} instance
* that uses the shortcuts
*/
public BeanInstanceSupplier withShortcuts(String... beanNames) {
return new BeanInstanceSupplier(this.lookup, this.generator, beanNames);
}
@Override
public Object get(RegisteredBean registeredBean) throws Exception {
Assert.notNull(registeredBean, "'registeredBean' must not be null"); Assert.notNull(registeredBean, "'registeredBean' must not be null");
Assert.notNull(generator, "'action' must not be null"); Executable executable = this.lookup.get(registeredBean);
AutowiredArguments resolved = resolveArguments(registeredBean, AutowiredArguments arguments = resolveArguments(registeredBean, executable);
this.lookup.get(registeredBean)); if (this.generator != null) {
return generator.apply(resolved); return this.generator.apply(registeredBean, arguments);
}
else {
return instantiate(registeredBean.getBeanFactory(), executable,
arguments.toArray());
}
}
@Nullable
@Override
public Method getFactoryMethod() {
if (this.lookup instanceof FactoryMethodLookup factoryMethodLookup) {
return factoryMethodLookup.get();
}
return null;
} }
/** /**
@ -161,43 +226,11 @@ public final class AutowiredInstantiationArgumentsResolver extends AutowiredElem
* @param registeredBean the registered bean * @param registeredBean the registered bean
* @return the resolved constructor or factory method arguments * @return the resolved constructor or factory method arguments
*/ */
public AutowiredArguments resolve(RegisteredBean registeredBean) { AutowiredArguments resolveArguments(RegisteredBean registeredBean) {
Assert.notNull(registeredBean, "'registeredBean' must not be null"); Assert.notNull(registeredBean, "'registeredBean' must not be null");
return resolveArguments(registeredBean, this.lookup.get(registeredBean)); return resolveArguments(registeredBean, this.lookup.get(registeredBean));
} }
/**
* Resolve arguments for the specified registered bean and instantiate a new
* instance using reflection.
* @param registeredBean the registered bean
* @return an instance of the bean
*/
@SuppressWarnings("unchecked")
public <T> T resolveAndInstantiate(RegisteredBean registeredBean) {
return (T) resolveAndInstantiate(registeredBean, Object.class);
}
/**
* Resolve arguments for the specified registered bean and instantiate a new
* instance using reflection.
* @param registeredBean the registered bean
* @param requiredType the required result type
* @return an instance of the bean
*/
@SuppressWarnings("unchecked")
public <T> T resolveAndInstantiate(RegisteredBean registeredBean,
Class<T> requiredType) {
Assert.notNull(registeredBean, "'registeredBean' must not be null");
Assert.notNull(registeredBean, "'requiredType' must not be null");
Executable executable = this.lookup.get(registeredBean);
AutowiredArguments arguments = resolveArguments(registeredBean, executable);
Object instance = instantiate(registeredBean.getBeanFactory(), executable,
arguments.toArray());
Assert.isInstanceOf(requiredType, instance);
return (T) instance;
}
private AutowiredArguments resolveArguments(RegisteredBean registeredBean, private AutowiredArguments resolveArguments(RegisteredBean registeredBean,
Executable executable) { Executable executable) {
@ -233,9 +266,6 @@ public final class AutowiredInstantiationArgumentsResolver extends AutowiredElem
autowiredBeans, parameter, dependencyDescriptor, argumentValue); autowiredBeans, parameter, dependencyDescriptor, argumentValue);
} }
registerDependentBeans(beanFactory, beanName, autowiredBeans); registerDependentBeans(beanFactory, beanName, autowiredBeans);
if (executable instanceof Method method) {
mergedBeanDefinition.setResolvedFactoryMethod(method);
}
return AutowiredArguments.of(resolved); return AutowiredArguments.of(resolved);
} }
@ -403,10 +433,12 @@ public final class AutowiredInstantiationArgumentsResolver extends AutowiredElem
private final Class<?>[] parameterTypes; private final Class<?>[] parameterTypes;
ConstructorLookup(Class<?>[] parameterTypes) { ConstructorLookup(Class<?>[] parameterTypes) {
this.parameterTypes = parameterTypes; this.parameterTypes = parameterTypes;
} }
@Override @Override
public Executable get(RegisteredBean registeredBean) { public Executable get(RegisteredBean registeredBean) {
Class<?> beanClass = registeredBean.getBeanClass(); Class<?> beanClass = registeredBean.getBeanClass();
@ -453,6 +485,10 @@ public final class AutowiredInstantiationArgumentsResolver extends AutowiredElem
@Override @Override
public Executable get(RegisteredBean registeredBean) { public Executable get(RegisteredBean registeredBean) {
return get();
}
Method get() {
Method method = ReflectionUtils.findMethod(this.declaringClass, Method method = ReflectionUtils.findMethod(this.declaringClass,
this.methodName, this.parameterTypes); this.methodName, this.parameterTypes);
Assert.notNull(method, () -> String.format("%s cannot be found", this)); Assert.notNull(method, () -> String.format("%s cannot be found", this));

View File

@ -35,17 +35,22 @@ import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.javapoet.ClassName; import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.CodeBlock.Builder;
import org.springframework.javapoet.MethodSpec; import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.MethodSpec.Builder;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.function.ThrowingSupplier; import org.springframework.util.function.ThrowingSupplier;
/** /**
* Internal code generator to create an {@link InstanceSupplier}. * Internal code generator to create an {@link InstanceSupplier}, usually in
* the form of a {@link BeanInstanceSupplier} that retains the executable
* that is used to instantiate the bean.
* <p> * <p>
* Generates code in the form:<pre class="code">{@code * Generated code is usually a method reference that generate the
* InstanceSupplier.of(TheGeneratedClass::getMyBeanInstance); * {@link BeanInstanceSupplier}, but some shortcut can be used as well such
* }</pre> * as:
* <pre class="code">
* {@code InstanceSupplier.of(TheGeneratedClass::getMyBeanInstance);}
* </pre>
* *
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll * @author Stephane Nicoll
@ -55,6 +60,8 @@ class InstanceSupplierCodeGenerator {
private static final String REGISTERED_BEAN_PARAMETER_NAME = "registeredBean"; private static final String REGISTERED_BEAN_PARAMETER_NAME = "registeredBean";
private static final String ARGS_PARAMETER_NAME = "args";
private static final javax.lang.model.element.Modifier[] PRIVATE_STATIC = { private static final javax.lang.model.element.Modifier[] PRIVATE_STATIC = {
javax.lang.model.element.Modifier.PRIVATE, javax.lang.model.element.Modifier.PRIVATE,
javax.lang.model.element.Modifier.STATIC }; javax.lang.model.element.Modifier.STATIC };
@ -109,15 +116,14 @@ class InstanceSupplierCodeGenerator {
constructor); constructor);
if (accessVisibility == AccessVisibility.PUBLIC if (accessVisibility == AccessVisibility.PUBLIC
|| accessVisibility == AccessVisibility.PACKAGE_PRIVATE) { || accessVisibility == AccessVisibility.PACKAGE_PRIVATE) {
return generateCodeForAccessibleConstructor(name, constructor, declaringClass, return generateCodeForAccessibleConstructor(name, constructor, dependsOnBean,
dependsOnBean); declaringClass);
} }
return generateCodeForInaccessibleConstructor(name, constructor, declaringClass, return generateCodeForInaccessibleConstructor(name, constructor, dependsOnBean);
dependsOnBean);
} }
private CodeBlock generateCodeForAccessibleConstructor(String name, private CodeBlock generateCodeForAccessibleConstructor(String name,
Constructor<?> constructor, Class<?> declaringClass, boolean dependsOnBean) { Constructor<?> constructor, boolean dependsOnBean, Class<?> declaringClass) {
this.generationContext.getRuntimeHints().reflection() this.generationContext.getRuntimeHints().reflection()
.registerConstructor(constructor, INTROSPECT); .registerConstructor(constructor, INTROSPECT);
@ -132,60 +138,47 @@ class InstanceSupplierCodeGenerator {
return CodeBlock.of("$T.of($T::new)", ThrowingSupplier.class, return CodeBlock.of("$T.of($T::new)", ThrowingSupplier.class,
declaringClass); declaringClass);
} }
GeneratedMethod generatedMethod = generateGetInstanceMethod(method -> GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method ->
buildGetInstanceMethodForConstructor(method, name, constructor, declaringClass, buildGetInstanceMethodForConstructor(method, name, constructor,
dependsOnBean, PRIVATE_STATIC)); declaringClass, dependsOnBean, PRIVATE_STATIC));
return CodeBlock.of("$T.of($T::$L)", InstanceSupplier.class, this.className, return generateReturnStatement(generatedMethod);
generatedMethod.getName());
} }
private CodeBlock generateCodeForInaccessibleConstructor(String name, private CodeBlock generateCodeForInaccessibleConstructor(String name,
Constructor<?> constructor, Class<?> declaringClass, boolean dependsOnBean) { Constructor<?> constructor, boolean dependsOnBean) {
this.generationContext.getRuntimeHints().reflection() this.generationContext.getRuntimeHints().reflection()
.registerConstructor(constructor); .registerConstructor(constructor);
GeneratedMethod generatedMethod = generateGetInstanceMethod(method -> { GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method -> {
method.addJavadoc("Instantiate the bean instance for '$L'.", name); method.addJavadoc("Get the bean instance supplier for '$L'.", name);
method.addModifiers(PRIVATE_STATIC); method.addModifiers(PRIVATE_STATIC);
method.returns(declaringClass); method.returns(BeanInstanceSupplier.class);
method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER_NAME);
int parameterOffset = (!dependsOnBean) ? 0 : 1; int parameterOffset = (!dependsOnBean) ? 0 : 1;
method.addStatement( method.addStatement(
generateResolverForConstructor(constructor, parameterOffset)); generateResolverForConstructor(constructor, parameterOffset));
method.addStatement("return resolver.resolveAndInstantiate($L)",
REGISTERED_BEAN_PARAMETER_NAME);
}); });
return CodeBlock.of("$T.of($T::$L)", InstanceSupplier.class, this.className, return generateReturnStatement(generatedMethod);
generatedMethod.getName());
} }
private void buildGetInstanceMethodForConstructor(MethodSpec.Builder method, private void buildGetInstanceMethodForConstructor(MethodSpec.Builder method,
String name, Constructor<?> constructor, Class<?> declaringClass, String name, Constructor<?> constructor, Class<?> declaringClass,
boolean dependsOnBean, javax.lang.model.element.Modifier... modifiers) { boolean dependsOnBean, javax.lang.model.element.Modifier... modifiers) {
method.addJavadoc("Create the bean instance for '$L'.", name); method.addJavadoc("Get the bean instance supplier for '$L'.", name);
method.addModifiers(modifiers); method.addModifiers(modifiers);
method.returns(declaringClass); method.returns(BeanInstanceSupplier.class);
method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER_NAME); int parameterOffset = (!dependsOnBean) ? 0 : 1;
if (constructor.getParameterCount() == 0) { CodeBlock.Builder code = CodeBlock.builder();
CodeBlock instantiationCode = generateNewInstanceCodeForConstructor( code.add(generateResolverForConstructor(constructor, parameterOffset));
dependsOnBean, declaringClass, NO_ARGS); boolean hasArguments = constructor.getParameterCount() > 0;
method.addCode(generateReturnStatement(instantiationCode)); CodeBlock arguments = hasArguments
} ? new AutowiredArgumentsCodeGenerator(declaringClass, constructor)
else { .generateCode(constructor.getParameterTypes(), parameterOffset)
int parameterOffset = (!dependsOnBean) ? 0 : 1; : NO_ARGS;
CodeBlock.Builder code = CodeBlock.builder(); CodeBlock newInstance = generateNewInstanceCodeForConstructor(dependsOnBean,
code.addStatement( declaringClass, arguments);
generateResolverForConstructor(constructor, parameterOffset)); code.add(generateWithGeneratorCode(hasArguments, newInstance));
CodeBlock arguments = new AutowiredArgumentsCodeGenerator(declaringClass, method.addStatement(code.build());
constructor).generateCode(constructor.getParameterTypes(),
parameterOffset);
CodeBlock newInstance = generateNewInstanceCodeForConstructor(dependsOnBean,
declaringClass, arguments);
code.addStatement("return resolver.resolve($L, (args) -> $L)",
REGISTERED_BEAN_PARAMETER_NAME, newInstance);
method.addCode(code.build());
}
} }
private CodeBlock generateResolverForConstructor(Constructor<?> constructor, private CodeBlock generateResolverForConstructor(Constructor<?> constructor,
@ -193,9 +186,8 @@ class InstanceSupplierCodeGenerator {
CodeBlock parameterTypes = generateParameterTypesCode( CodeBlock parameterTypes = generateParameterTypesCode(
constructor.getParameterTypes(), parameterOffset); constructor.getParameterTypes(), parameterOffset);
return CodeBlock.of("$T resolver = $T.forConstructor($L)", return CodeBlock.of("return $T.forConstructor($L)",
AutowiredInstantiationArgumentsResolver.class, BeanInstanceSupplier.class, parameterTypes);
AutowiredInstantiationArgumentsResolver.class, parameterTypes);
} }
private CodeBlock generateNewInstanceCodeForConstructor(boolean dependsOnBean, private CodeBlock generateNewInstanceCodeForConstructor(boolean dependsOnBean,
@ -232,21 +224,16 @@ class InstanceSupplierCodeGenerator {
this.generationContext.getRuntimeHints().reflection() this.generationContext.getRuntimeHints().reflection()
.registerMethod(factoryMethod, INTROSPECT); .registerMethod(factoryMethod, INTROSPECT);
if (!dependsOnBean && factoryMethod.getParameterCount() == 0) { if (!dependsOnBean && factoryMethod.getParameterCount() == 0) {
if (!this.allowDirectSupplierShortcut) { CodeBlock.Builder code = CodeBlock.builder();
return CodeBlock.of("$T.using($T::$L)", InstanceSupplier.class, code.add("$T.forFactoryMethod($T.class, $S)", BeanInstanceSupplier.class,
declaringClass, factoryMethod.getName()); declaringClass, factoryMethod.getName());
} code.add(".withGenerator($T::$L)", declaringClass, factoryMethod.getName());
if (!isThrowingCheckedException(factoryMethod)) { return code.build();
return CodeBlock.of("$T::$L", declaringClass, factoryMethod.getName());
}
return CodeBlock.of("$T.of($T::$L)", ThrowingSupplier.class, declaringClass,
factoryMethod.getName());
} }
GeneratedMethod generatedMethod = generateGetInstanceMethod(method -> GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method ->
buildGetInstanceMethodForFactoryMethod(method, name, factoryMethod, declaringClass, buildGetInstanceMethodForFactoryMethod(method, name, factoryMethod,
dependsOnBean, PRIVATE_STATIC)); declaringClass, dependsOnBean, PRIVATE_STATIC));
return CodeBlock.of("$T.of($T::$L)", InstanceSupplier.class, this.className, return generateReturnStatement(getInstanceMethod);
generatedMethod.getName());
} }
private CodeBlock generateCodeForInaccessibleFactoryMethod(String name, private CodeBlock generateCodeForInaccessibleFactoryMethod(String name,
@ -254,18 +241,14 @@ class InstanceSupplierCodeGenerator {
this.generationContext.getRuntimeHints().reflection() this.generationContext.getRuntimeHints().reflection()
.registerMethod(factoryMethod); .registerMethod(factoryMethod);
GeneratedMethod generatedMethod = generateGetInstanceMethod(method -> { GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method -> {
method.addJavadoc("Instantiate the bean instance for '$L'.", name); method.addJavadoc("Get the bean instance supplier for '$L'.", name);
method.addModifiers(PRIVATE_STATIC); method.addModifiers(PRIVATE_STATIC);
method.returns(factoryMethod.getReturnType()); method.returns(BeanInstanceSupplier.class);
method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER_NAME); method.addStatement(generateInstanceSupplierForFactoryMethod(factoryMethod,
method.addStatement(generateResolverForFactoryMethod(factoryMethod,
declaringClass, factoryMethod.getName())); declaringClass, factoryMethod.getName()));
method.addStatement("return resolver.resolveAndInstantiate($L)",
REGISTERED_BEAN_PARAMETER_NAME);
}); });
return CodeBlock.of("$T.of($T::$L)", InstanceSupplier.class, this.className, return generateReturnStatement(getInstanceMethod);
generatedMethod.getName());
} }
private void buildGetInstanceMethodForFactoryMethod(MethodSpec.Builder method, private void buildGetInstanceMethodForFactoryMethod(MethodSpec.Builder method,
@ -273,46 +256,34 @@ class InstanceSupplierCodeGenerator {
boolean dependsOnBean, javax.lang.model.element.Modifier... modifiers) { boolean dependsOnBean, javax.lang.model.element.Modifier... modifiers) {
String factoryMethodName = factoryMethod.getName(); String factoryMethodName = factoryMethod.getName();
method.addJavadoc("Get the bean instance for '$L'.", name); method.addJavadoc("Get the bean instance supplier for '$L'.", name);
method.addModifiers(modifiers); method.addModifiers(modifiers);
method.returns(factoryMethod.getReturnType()); method.returns(BeanInstanceSupplier.class);
if (isThrowingCheckedException(factoryMethod)) { CodeBlock.Builder code = CodeBlock.builder();
method.addException(Exception.class); code.add(generateInstanceSupplierForFactoryMethod(factoryMethod, declaringClass, factoryMethodName));
} boolean hasArguments = factoryMethod.getParameterCount() > 0;
method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER_NAME); CodeBlock arguments = hasArguments
if (factoryMethod.getParameterCount() == 0) { ? new AutowiredArgumentsCodeGenerator(declaringClass, factoryMethod)
CodeBlock instantiationCode = generateNewInstanceCodeForMethod(dependsOnBean, .generateCode(factoryMethod.getParameterTypes())
declaringClass, factoryMethodName, NO_ARGS); : NO_ARGS;
method.addCode(generateReturnStatement(instantiationCode)); CodeBlock newInstance = generateNewInstanceCodeForMethod(dependsOnBean,
} declaringClass, factoryMethodName, arguments);
else { code.add(generateWithGeneratorCode(hasArguments, newInstance));
CodeBlock.Builder code = CodeBlock.builder(); method.addStatement(code.build());
code.addStatement(generateResolverForFactoryMethod(factoryMethod,
declaringClass, factoryMethodName));
CodeBlock arguments = new AutowiredArgumentsCodeGenerator(declaringClass,
factoryMethod).generateCode(factoryMethod.getParameterTypes());
CodeBlock newInstance = generateNewInstanceCodeForMethod(dependsOnBean,
declaringClass, factoryMethodName, arguments);
code.addStatement("return resolver.resolve($L, (args) -> $L)",
REGISTERED_BEAN_PARAMETER_NAME, newInstance);
method.addCode(code.build());
}
} }
private CodeBlock generateResolverForFactoryMethod(Method factoryMethod, private CodeBlock generateInstanceSupplierForFactoryMethod(Method factoryMethod,
Class<?> declaringClass, String factoryMethodName) { Class<?> declaringClass, String factoryMethodName) {
if (factoryMethod.getParameterCount() == 0) { if (factoryMethod.getParameterCount() == 0) {
return CodeBlock.of("$T resolver = $T.forFactoryMethod($T.class, $S)", return CodeBlock.of("return $T.forFactoryMethod($T.class, $S)",
AutowiredInstantiationArgumentsResolver.class, BeanInstanceSupplier.class, declaringClass,
AutowiredInstantiationArgumentsResolver.class, declaringClass,
factoryMethodName); factoryMethodName);
} }
CodeBlock parameterTypes = generateParameterTypesCode( CodeBlock parameterTypes = generateParameterTypesCode(
factoryMethod.getParameterTypes(), 0); factoryMethod.getParameterTypes(), 0);
return CodeBlock.of("$T resolver = $T.forFactoryMethod($T.class, $S, $L)", return CodeBlock.of("return $T.forFactoryMethod($T.class, $S, $L)",
AutowiredInstantiationArgumentsResolver.class, BeanInstanceSupplier.class, declaringClass,
AutowiredInstantiationArgumentsResolver.class, declaringClass,
factoryMethodName, parameterTypes); factoryMethodName, parameterTypes);
} }
@ -326,9 +297,19 @@ class InstanceSupplierCodeGenerator {
REGISTERED_BEAN_PARAMETER_NAME, declaringClass, factoryMethodName, args); REGISTERED_BEAN_PARAMETER_NAME, declaringClass, factoryMethodName, args);
} }
private CodeBlock generateReturnStatement(CodeBlock instantiationCode) { private CodeBlock generateReturnStatement(GeneratedMethod getInstanceMethod) {
CodeBlock.Builder code = CodeBlock.builder(); return CodeBlock.of("$T.$L()", this.className, getInstanceMethod.getName());
code.addStatement("return $L", instantiationCode); }
private CodeBlock generateWithGeneratorCode(boolean hasArguments, CodeBlock newInstance) {
CodeBlock lambdaArguments = (hasArguments
? CodeBlock.of("($L, $L)", REGISTERED_BEAN_PARAMETER_NAME, ARGS_PARAMETER_NAME)
: CodeBlock.of("($L)", REGISTERED_BEAN_PARAMETER_NAME));
Builder code = CodeBlock.builder();
code.add("\n");
code.indent().indent();
code.add(".withGenerator($L -> $L)", lambdaArguments, newInstance);
code.unindent().unindent();
return code.build(); return code.build();
} }
@ -342,16 +323,16 @@ class InstanceSupplierCodeGenerator {
} }
private CodeBlock generateParameterTypesCode(Class<?>[] parameterTypes, int offset) { private CodeBlock generateParameterTypesCode(Class<?>[] parameterTypes, int offset) {
CodeBlock.Builder builder = CodeBlock.builder(); CodeBlock.Builder code = CodeBlock.builder();
for (int i = offset; i < parameterTypes.length; i++) { for (int i = offset; i < parameterTypes.length; i++) {
builder.add(i != offset ? ", " : ""); code.add(i != offset ? ", " : "");
builder.add("$T.class", parameterTypes[i]); code.add("$T.class", parameterTypes[i]);
} }
return builder.build(); return code.build();
} }
private GeneratedMethod generateGetInstanceMethod(Consumer<Builder> method) { private GeneratedMethod generateGetInstanceSupplierMethod(Consumer<MethodSpec.Builder> method) {
return this.generatedMethods.add("getInstance", method); return this.generatedMethods.add("getInstanceSupplier", method);
} }
private boolean isThrowingCheckedException(Executable executable) { private boolean isThrowingCheckedException(Executable executable) {

View File

@ -16,8 +16,10 @@
package org.springframework.beans.factory.support; package org.springframework.beans.factory.support;
import java.lang.reflect.Method;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.function.ThrowingBiFunction; import org.springframework.util.function.ThrowingBiFunction;
import org.springframework.util.function.ThrowingSupplier; import org.springframework.util.function.ThrowingSupplier;
@ -29,6 +31,7 @@ import org.springframework.util.function.ThrowingSupplier;
* supply the instance. * supply the instance.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll
* @since 6.0 * @since 6.0
* @param <T> the type of instance supplied by this supplier * @param <T> the type of instance supplied by this supplier
* @see RegisteredBean * @see RegisteredBean
@ -49,6 +52,17 @@ public interface InstanceSupplier<T> extends ThrowingSupplier<T> {
*/ */
T get(RegisteredBean registeredBean) throws Exception; T get(RegisteredBean registeredBean) throws Exception;
/**
* Return the factory method that this supplier uses to create the
* instance, or {@code null} if it is not known or this supplier uses
* another mean.
* @return the factory method used to create the instance, or {@code null}
*/
@Nullable
default Method getFactoryMethod() {
return null;
}
/** /**
* Return a composed instance supplier that first obtains the instance from * Return a composed instance supplier that first obtains the instance from
* this supplier, and then applied the {@code after} function to obtain the * this supplier, and then applied the {@code after} function to obtain the

View File

@ -429,6 +429,16 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
return this.factoryMethodToIntrospect; return this.factoryMethodToIntrospect;
} }
@Override
public void setInstanceSupplier(@Nullable Supplier<?> instanceSupplier) {
super.setInstanceSupplier(instanceSupplier);
Method factoryMethod = (instanceSupplier instanceof InstanceSupplier<?>)
? ((InstanceSupplier<?>) instanceSupplier).getFactoryMethod() : null;
if (factoryMethod != null) {
setResolvedFactoryMethod(factoryMethod);
}
}
/** /**
* Register an externally managed configuration method or field. * Register an externally managed configuration method or field.
*/ */

View File

@ -20,6 +20,7 @@ import java.io.InputStream;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Executable; import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -39,7 +40,7 @@ import org.springframework.beans.factory.BeanCurrentlyInCreationException;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.aot.AutowiredInstantiationArgumentsResolverTests.Enclosing.InnerSingleArgConstructor; import org.springframework.beans.factory.aot.BeanInstanceSupplierTests.Enclosing.InnerSingleArgConstructor;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.DependencyDescriptor;
@ -54,6 +55,10 @@ import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.function.ThrowingBiFunction;
import org.springframework.util.function.ThrowingFunction;
import org.springframework.util.function.ThrowingSupplier;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@ -62,19 +67,19 @@ import static org.assertj.core.api.Assertions.entry;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link AutowiredInstantiationArgumentsResolver}. * Tests for {@link BeanInstanceSupplier}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll * @author Stephane Nicoll
*/ */
class AutowiredInstantiationArgumentsResolverTests { class BeanInstanceSupplierTests {
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
@Test @Test
void forConstructorWhenParameterTypesIsNullThrowsException() { void forConstructorWhenParameterTypesIsNullThrowsException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException()
.isThrownBy(() -> AutowiredInstantiationArgumentsResolver .isThrownBy(() -> BeanInstanceSupplier
.forConstructor((Class<?>[]) null)) .forConstructor((Class<?>[]) null))
.withMessage("'parameterTypes' must not be null"); .withMessage("'parameterTypes' must not be null");
} }
@ -82,27 +87,33 @@ class AutowiredInstantiationArgumentsResolverTests {
@Test @Test
void forConstructorWhenParameterTypesContainsNullThrowsException() { void forConstructorWhenParameterTypesContainsNullThrowsException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException()
.isThrownBy(() -> AutowiredInstantiationArgumentsResolver .isThrownBy(() -> BeanInstanceSupplier
.forConstructor(String.class, null)) .forConstructor(String.class, null))
.withMessage("'parameterTypes' must not contain null elements"); .withMessage("'parameterTypes' must not contain null elements");
} }
@Test @Test
void forConstructorWhenNotFoundThrowsException() { void forConstructorWhenNotFoundThrowsException() {
AutowiredInstantiationArgumentsResolver resolver = AutowiredInstantiationArgumentsResolver BeanInstanceSupplier resolver = BeanInstanceSupplier
.forConstructor(InputStream.class); .forConstructor(InputStream.class);
Source source = new Source(SingleArgConstructor.class, resolver); Source source = new Source(SingleArgConstructor.class, resolver);
RegisteredBean registerBean = source.registerBean(this.beanFactory); RegisteredBean registerBean = source.registerBean(this.beanFactory);
assertThatIllegalArgumentException() assertThatIllegalArgumentException()
.isThrownBy(() -> resolver.resolve(registerBean)).withMessage( .isThrownBy(() -> resolver.get(registerBean)).withMessage(
"Constructor with parameter types [java.io.InputStream] cannot be found on " "Constructor with parameter types [java.io.InputStream] cannot be found on "
+ SingleArgConstructor.class.getName()); + SingleArgConstructor.class.getName());
} }
@Test
void forConstructorReturnsNullFactoryMethod() {
BeanInstanceSupplier resolver = BeanInstanceSupplier.forConstructor(String.class);
assertThat(resolver.getFactoryMethod()).isNull();
}
@Test @Test
void forFactoryMethodWhenDeclaringClassIsNullThrowsException() { void forFactoryMethodWhenDeclaringClassIsNullThrowsException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException()
.isThrownBy(() -> AutowiredInstantiationArgumentsResolver .isThrownBy(() -> BeanInstanceSupplier
.forFactoryMethod(null, "test")) .forFactoryMethod(null, "test"))
.withMessage("'declaringClass' must not be null"); .withMessage("'declaringClass' must not be null");
} }
@ -110,7 +121,7 @@ class AutowiredInstantiationArgumentsResolverTests {
@Test @Test
void forFactoryMethodWhenNameIsEmptyThrowsException() { void forFactoryMethodWhenNameIsEmptyThrowsException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException()
.isThrownBy(() -> AutowiredInstantiationArgumentsResolver .isThrownBy(() -> BeanInstanceSupplier
.forFactoryMethod(SingleArgFactory.class, "")) .forFactoryMethod(SingleArgFactory.class, ""))
.withMessage("'methodName' must not be empty"); .withMessage("'methodName' must not be empty");
} }
@ -119,7 +130,7 @@ class AutowiredInstantiationArgumentsResolverTests {
void forFactoryMethodWhenParameterTypesIsNullThrowsException() { void forFactoryMethodWhenParameterTypesIsNullThrowsException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException()
.isThrownBy( .isThrownBy(
() -> AutowiredInstantiationArgumentsResolver.forFactoryMethod( () -> BeanInstanceSupplier.forFactoryMethod(
SingleArgFactory.class, "single", (Class<?>[]) null)) SingleArgFactory.class, "single", (Class<?>[]) null))
.withMessage("'parameterTypes' must not be null"); .withMessage("'parameterTypes' must not be null");
} }
@ -128,61 +139,136 @@ class AutowiredInstantiationArgumentsResolverTests {
void forFactoryMethodWhenParameterTypesContainsNullThrowsException() { void forFactoryMethodWhenParameterTypesContainsNullThrowsException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException()
.isThrownBy( .isThrownBy(
() -> AutowiredInstantiationArgumentsResolver.forFactoryMethod( () -> BeanInstanceSupplier.forFactoryMethod(
SingleArgFactory.class, "single", String.class, null)) SingleArgFactory.class, "single", String.class, null))
.withMessage("'parameterTypes' must not contain null elements"); .withMessage("'parameterTypes' must not contain null elements");
} }
@Test @Test
void forFactoryMethodWhenNotFoundThrowsException() { void forFactoryMethodWhenNotFoundThrowsException() {
AutowiredInstantiationArgumentsResolver resolver = AutowiredInstantiationArgumentsResolver BeanInstanceSupplier resolver = BeanInstanceSupplier
.forFactoryMethod(SingleArgFactory.class, "single", InputStream.class); .forFactoryMethod(SingleArgFactory.class, "single", InputStream.class);
Source source = new Source(String.class, resolver); Source source = new Source(String.class, resolver);
RegisteredBean registerBean = source.registerBean(this.beanFactory); RegisteredBean registerBean = source.registerBean(this.beanFactory);
assertThatIllegalArgumentException() assertThatIllegalArgumentException()
.isThrownBy(() -> resolver.resolve(registerBean)).withMessage( .isThrownBy(() -> resolver.get(registerBean)).withMessage(
"Factory method 'single' with parameter types [java.io.InputStream] declared on class " "Factory method 'single' with parameter types [java.io.InputStream] declared on class "
+ SingleArgFactory.class.getName() + " cannot be found"); + SingleArgFactory.class.getName() + " cannot be found");
} }
@Test @Test
void resolveWithActionWhenActionIsNullThrowsException() { void forFactoryMethodReturnsFactoryMethod() {
AutowiredInstantiationArgumentsResolver resolver = AutowiredInstantiationArgumentsResolver BeanInstanceSupplier resolver = BeanInstanceSupplier
.forConstructor(); .forFactoryMethod(SingleArgFactory.class, "single", String.class);
Source source = new Source(NoArgConstructor.class, resolver); Method factoryMethod = ReflectionUtils.findMethod(SingleArgFactory.class, "single", String.class);
RegisteredBean registerBean = source.registerBean(this.beanFactory); assertThat(factoryMethod).isNotNull();
assertThatIllegalArgumentException() assertThat(resolver.getFactoryMethod()).isEqualTo(factoryMethod);
.isThrownBy(() -> resolver.resolve(registerBean, null))
.withMessage("'action' must not be null");
} }
@Test @Test
void resolveWithActionCallsAction() { void withGeneratorWhenBiFunctionIsNullThrowsException() {
AutowiredInstantiationArgumentsResolver resolver = AutowiredInstantiationArgumentsResolver BeanInstanceSupplier resolver = BeanInstanceSupplier
.forConstructor();
assertThatIllegalArgumentException()
.isThrownBy(() -> resolver.withGenerator(
(ThrowingBiFunction<RegisteredBean, AutowiredArguments, Object>) null))
.withMessage("'generator' must not be null");
}
@Test
void withGeneratorWhenFunctionIsNullThrowsException() {
BeanInstanceSupplier resolver = BeanInstanceSupplier
.forConstructor();
assertThatIllegalArgumentException()
.isThrownBy(() -> resolver.withGenerator(
(ThrowingFunction<RegisteredBean, Object>) null))
.withMessage("'generator' must not be null");
}
@Test
void withGeneratorWhenSupplierIsNullThrowsException() {
BeanInstanceSupplier resolver = BeanInstanceSupplier
.forConstructor();
assertThatIllegalArgumentException()
.isThrownBy(() -> resolver.withGenerator(
(ThrowingSupplier<Object>) null))
.withMessage("'generator' must not be null");
}
@Test
void getWithConstructorDoesNotSetResolvedFactoryMethod() throws Exception {
BeanInstanceSupplier resolver = BeanInstanceSupplier
.forConstructor(String.class); .forConstructor(String.class);
Source source = new Source(SingleArgConstructor.class, resolver);
this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("one", "1");
Source source = new Source(SingleArgConstructor.class, resolver);
RegisteredBean registerBean = source.registerBean(this.beanFactory); RegisteredBean registerBean = source.registerBean(this.beanFactory);
assertThat(registerBean.getMergedBeanDefinition().getResolvedFactoryMethod()).isNull();
source.getResolver().get(registerBean);
assertThat(registerBean.getMergedBeanDefinition().getResolvedFactoryMethod()).isNull();
}
@Test
void getWithFactoryMethodSetsResolvedFactoryMethod() {
Method factoryMethod = ReflectionUtils.findMethod(SingleArgFactory.class, "single", String.class);
assertThat(factoryMethod).isNotNull();
BeanInstanceSupplier resolver = BeanInstanceSupplier
.forFactoryMethod(SingleArgFactory.class, "single", String.class);
RootBeanDefinition beanDefinition = new RootBeanDefinition(String.class);
assertThat(beanDefinition.getResolvedFactoryMethod()).isNull();
beanDefinition.setInstanceSupplier(resolver);
assertThat(beanDefinition.getResolvedFactoryMethod()).isEqualTo(factoryMethod);
}
@Test
void getWithGeneratorCallsBiFunction() throws Exception {
BeanRegistrar registrar = new BeanRegistrar(SingleArgConstructor.class);
this.beanFactory.registerSingleton("one", "1");
RegisteredBean registerBean = registrar.registerBean(this.beanFactory);
List<Object> result = new ArrayList<>(); List<Object> result = new ArrayList<>();
resolver.resolve(registerBean, result::add); BeanInstanceSupplier resolver = BeanInstanceSupplier
.forConstructor(String.class)
.withGenerator((registeredBean, args) -> result.add(args));
resolver.get(registerBean);
assertThat(result).hasSize(1); assertThat(result).hasSize(1);
assertThat(((AutowiredArguments) result.get(0)).toArray()).containsExactly("1"); assertThat(((AutowiredArguments) result.get(0)).toArray()).containsExactly("1");
} }
@Test @Test
void resolveWhenRegisteredBeanIsNullThrowsException() { void getWithGeneratorCallsFunction() throws Exception {
AutowiredInstantiationArgumentsResolver resolver = AutowiredInstantiationArgumentsResolver BeanRegistrar registrar = new BeanRegistrar(SingleArgConstructor.class);
this.beanFactory.registerSingleton("one", "1");
RegisteredBean registerBean = registrar.registerBean(this.beanFactory);
BeanInstanceSupplier resolver = BeanInstanceSupplier
.forConstructor(String.class)
.withGenerator(registeredBean -> "1");
assertThat(resolver.get(registerBean)).isInstanceOf(String.class).isEqualTo("1");
}
@Test
void getWithGeneratorCallsSupplier() throws Exception {
BeanRegistrar registrar = new BeanRegistrar(SingleArgConstructor.class);
this.beanFactory.registerSingleton("one", "1");
RegisteredBean registerBean = registrar.registerBean(this.beanFactory);
BeanInstanceSupplier resolver = BeanInstanceSupplier
.forConstructor(String.class)
.withGenerator(() -> "1");
assertThat(resolver.get(registerBean)).isInstanceOf(String.class).isEqualTo("1");
}
@Test
void getWhenRegisteredBeanIsNullThrowsException() {
BeanInstanceSupplier resolver = BeanInstanceSupplier
.forConstructor(String.class); .forConstructor(String.class);
assertThatIllegalArgumentException().isThrownBy(() -> resolver.resolve(null)) assertThatIllegalArgumentException().isThrownBy(() -> resolver.get((RegisteredBean) null))
.withMessage("'registeredBean' must not be null"); .withMessage("'registeredBean' must not be null");
} }
@ParameterizedResolverTest(Sources.SINGLE_ARG) @ParameterizedResolverTest(Sources.SINGLE_ARG)
void resolveAndInstantiate(Source source) { void getWithNoGeneratorUsesReflection(Source source) throws Exception {
this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("one", "1");
this.beanFactory.registerSingleton("testFactory", new SingleArgFactory()); this.beanFactory.registerSingleton("testFactory", new SingleArgFactory());
RegisteredBean registerBean = source.registerBean(this.beanFactory); RegisteredBean registerBean = source.registerBean(this.beanFactory);
Object instance = source.getResolver().resolveAndInstantiate(registerBean); Object instance = source.getResolver().get(registerBean);
if (instance instanceof SingleArgConstructor singleArgConstructor) { if (instance instanceof SingleArgConstructor singleArgConstructor) {
instance = singleArgConstructor.getString(); instance = singleArgConstructor.getString();
} }
@ -190,12 +276,12 @@ class AutowiredInstantiationArgumentsResolverTests {
} }
@ParameterizedResolverTest(Sources.INNER_CLASS_SINGLE_ARG) @ParameterizedResolverTest(Sources.INNER_CLASS_SINGLE_ARG)
void resolveAndInstantiateNested(Source source) { void getNestedWithNoGeneratorUsesReflection(Source source) throws Exception {
this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("one", "1");
this.beanFactory.registerSingleton("testFactory", this.beanFactory.registerSingleton("testFactory",
new Enclosing().new InnerSingleArgFactory()); new Enclosing().new InnerSingleArgFactory());
RegisteredBean registerBean = source.registerBean(this.beanFactory); RegisteredBean registerBean = source.registerBean(this.beanFactory);
Object instance = source.getResolver().resolveAndInstantiate(registerBean); Object instance = source.getResolver().get(registerBean);
if (instance instanceof InnerSingleArgConstructor innerSingleArgConstructor) { if (instance instanceof InnerSingleArgConstructor innerSingleArgConstructor) {
instance = innerSingleArgConstructor.getString(); instance = innerSingleArgConstructor.getString();
} }
@ -203,38 +289,38 @@ class AutowiredInstantiationArgumentsResolverTests {
} }
@Test @Test
void resolveNoArgConstructor() { void resolveArgumentsWithNoArgConstructor() {
RootBeanDefinition beanDefinition = new RootBeanDefinition( RootBeanDefinition beanDefinition = new RootBeanDefinition(
NoArgConstructor.class); NoArgConstructor.class);
this.beanFactory.registerBeanDefinition("test", beanDefinition); this.beanFactory.registerBeanDefinition("test", beanDefinition);
RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "test"); RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "test");
AutowiredArguments resolved = AutowiredInstantiationArgumentsResolver AutowiredArguments resolved = BeanInstanceSupplier
.forConstructor().resolve(registeredBean); .forConstructor().resolveArguments(registeredBean);
assertThat(resolved.toArray()).isEmpty(); assertThat(resolved.toArray()).isEmpty();
} }
@ParameterizedResolverTest(Sources.SINGLE_ARG) @ParameterizedResolverTest(Sources.SINGLE_ARG)
void resolveSingleArgConstructor(Source source) { void resolveArgumentsWithSingleArgConstructor(Source source) {
this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("one", "1");
RegisteredBean registeredBean = source.registerBean(this.beanFactory); RegisteredBean registeredBean = source.registerBean(this.beanFactory);
assertThat(source.getResolver().resolve(registeredBean).toArray()) assertThat(source.getResolver().resolveArguments(registeredBean).toArray())
.containsExactly("1"); .containsExactly("1");
} }
@ParameterizedResolverTest(Sources.INNER_CLASS_SINGLE_ARG) @ParameterizedResolverTest(Sources.INNER_CLASS_SINGLE_ARG)
void resolvedNestedSingleArgConstructor(Source source) { void resolveArgumentsWithNestedSingleArgConstructor(Source source) {
this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("one", "1");
RegisteredBean registeredBean = source.registerBean(this.beanFactory); RegisteredBean registeredBean = source.registerBean(this.beanFactory);
assertThat(source.getResolver().resolve(registeredBean).toArray()) assertThat(source.getResolver().resolveArguments(registeredBean).toArray())
.containsExactly("1"); .containsExactly("1");
} }
@ParameterizedResolverTest(Sources.SINGLE_ARG) @ParameterizedResolverTest(Sources.SINGLE_ARG)
void resolveRequiredDependencyNotPresentThrowsUnsatisfiedDependencyException( void resolveArgumentsWithRequiredDependencyNotPresentThrowsUnsatisfiedDependencyException(
Source source) { Source source) {
RegisteredBean registeredBean = source.registerBean(this.beanFactory); RegisteredBean registeredBean = source.registerBean(this.beanFactory);
assertThatExceptionOfType(UnsatisfiedDependencyException.class) assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(() -> source.getResolver().resolve(registeredBean)) .isThrownBy(() -> source.getResolver().resolveArguments(registeredBean))
.satisfies(ex -> { .satisfies(ex -> {
assertThat(ex.getBeanName()).isEqualTo("testBean"); assertThat(ex.getBeanName()).isEqualTo("testBean");
assertThat(ex.getInjectionPoint()).isNotNull(); assertThat(ex.getInjectionPoint()).isNotNull();
@ -244,16 +330,16 @@ class AutowiredInstantiationArgumentsResolverTests {
} }
@Test @Test
void resolveInInstanceSupplierWithSelfReferenceThrowsException() { void resolveArgumentsInInstanceSupplierWithSelfReferenceThrowsException() {
// SingleArgFactory.single(...) expects a String to be injected // SingleArgFactory.single(...) expects a String to be injected
// and our own bean is a String so it's a valid candidate // and our own bean is a String, so it's a valid candidate
this.beanFactory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor()); this.beanFactory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
RootBeanDefinition beanDefinition = new RootBeanDefinition(String.class); RootBeanDefinition beanDefinition = new RootBeanDefinition(String.class);
beanDefinition.setInstanceSupplier(InstanceSupplier.of(registeredBean -> { beanDefinition.setInstanceSupplier(InstanceSupplier.of(registeredBean -> {
AutowiredArguments args = AutowiredInstantiationArgumentsResolver AutowiredArguments args = BeanInstanceSupplier
.forFactoryMethod(SingleArgFactory.class, "single", String.class) .forFactoryMethod(SingleArgFactory.class, "single", String.class)
.resolve(registeredBean); .resolveArguments(registeredBean);
return new SingleArgFactory().single((String) args.get(0)); return new SingleArgFactory().single(args.get(0));
})); }));
this.beanFactory.registerBeanDefinition("test", beanDefinition); this.beanFactory.registerBeanDefinition("test", beanDefinition);
assertThatExceptionOfType(UnsatisfiedDependencyException.class) assertThatExceptionOfType(UnsatisfiedDependencyException.class)
@ -261,83 +347,83 @@ class AutowiredInstantiationArgumentsResolverTests {
} }
@ParameterizedResolverTest(Sources.ARRAY_OF_BEANS) @ParameterizedResolverTest(Sources.ARRAY_OF_BEANS)
void resolveArrayOfBeans(Source source) { void resolveArgumentsWithArrayOfBeans(Source source) {
this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("one", "1");
this.beanFactory.registerSingleton("two", "2"); this.beanFactory.registerSingleton("two", "2");
RegisteredBean registerBean = source.registerBean(this.beanFactory); RegisteredBean registerBean = source.registerBean(this.beanFactory);
AutowiredArguments arguments = source.getResolver().resolve(registerBean); AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(1); assertThat(arguments.toArray()).hasSize(1);
assertThat((Object[]) arguments.get(0)).containsExactly("1", "2"); assertThat((Object[]) arguments.get(0)).containsExactly("1", "2");
} }
@ParameterizedResolverTest(Sources.ARRAY_OF_BEANS) @ParameterizedResolverTest(Sources.ARRAY_OF_BEANS)
void resolveRequiredArrayOfBeansInjectEmptyArray(Source source) { void resolveArgumentsWithRequiredArrayOfBeansInjectEmptyArray(Source source) {
RegisteredBean registerBean = source.registerBean(this.beanFactory); RegisteredBean registerBean = source.registerBean(this.beanFactory);
AutowiredArguments arguments = source.getResolver().resolve(registerBean); AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(1); assertThat(arguments.toArray()).hasSize(1);
assertThat((Object[]) arguments.get(0)).isEmpty(); assertThat((Object[]) arguments.get(0)).isEmpty();
} }
@ParameterizedResolverTest(Sources.LIST_OF_BEANS) @ParameterizedResolverTest(Sources.LIST_OF_BEANS)
void resolveListOfBeans(Source source) { void resolveArgumentsWithListOfBeans(Source source) {
this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("one", "1");
this.beanFactory.registerSingleton("two", "2"); this.beanFactory.registerSingleton("two", "2");
RegisteredBean registerBean = source.registerBean(this.beanFactory); RegisteredBean registerBean = source.registerBean(this.beanFactory);
AutowiredArguments arguments = source.getResolver().resolve(registerBean); AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(1); assertThat(arguments.toArray()).hasSize(1);
assertThat(arguments.getObject(0)).isInstanceOf(List.class).asList() assertThat(arguments.getObject(0)).isInstanceOf(List.class).asList()
.containsExactly("1", "2"); .containsExactly("1", "2");
} }
@ParameterizedResolverTest(Sources.LIST_OF_BEANS) @ParameterizedResolverTest(Sources.LIST_OF_BEANS)
void resolveRequiredListOfBeansInjectEmptyList(Source source) { void resolveArgumentsWithRequiredListOfBeansInjectEmptyList(Source source) {
RegisteredBean registerBean = source.registerBean(this.beanFactory); RegisteredBean registerBean = source.registerBean(this.beanFactory);
AutowiredArguments arguments = source.getResolver().resolve(registerBean); AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(1); assertThat(arguments.toArray()).hasSize(1);
assertThat((List<?>) arguments.get(0)).isEmpty(); assertThat((List<?>) arguments.get(0)).isEmpty();
} }
@ParameterizedResolverTest(Sources.SET_OF_BEANS) @ParameterizedResolverTest(Sources.SET_OF_BEANS)
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
void resolveSetOfBeans(Source source) { void resolveArgumentsWithSetOfBeans(Source source) {
this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("one", "1");
this.beanFactory.registerSingleton("two", "2"); this.beanFactory.registerSingleton("two", "2");
RegisteredBean registerBean = source.registerBean(this.beanFactory); RegisteredBean registerBean = source.registerBean(this.beanFactory);
AutowiredArguments arguments = source.getResolver().resolve(registerBean); AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(1); assertThat(arguments.toArray()).hasSize(1);
assertThat((Set<String>) arguments.get(0)).containsExactly("1", "2"); assertThat((Set<String>) arguments.get(0)).containsExactly("1", "2");
} }
@ParameterizedResolverTest(Sources.SET_OF_BEANS) @ParameterizedResolverTest(Sources.SET_OF_BEANS)
void resolveRequiredSetOfBeansInjectEmptySet(Source source) { void resolveArgumentsWithRequiredSetOfBeansInjectEmptySet(Source source) {
RegisteredBean registerBean = source.registerBean(this.beanFactory); RegisteredBean registerBean = source.registerBean(this.beanFactory);
AutowiredArguments arguments = source.getResolver().resolve(registerBean); AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(1); assertThat(arguments.toArray()).hasSize(1);
assertThat((Set<?>) arguments.get(0)).isEmpty(); assertThat((Set<?>) arguments.get(0)).isEmpty();
} }
@ParameterizedResolverTest(Sources.MAP_OF_BEANS) @ParameterizedResolverTest(Sources.MAP_OF_BEANS)
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
void resolveMapOfBeans(Source source) { void resolveArgumentsWithMapOfBeans(Source source) {
this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("one", "1");
this.beanFactory.registerSingleton("two", "2"); this.beanFactory.registerSingleton("two", "2");
RegisteredBean registerBean = source.registerBean(this.beanFactory); RegisteredBean registerBean = source.registerBean(this.beanFactory);
AutowiredArguments arguments = source.getResolver().resolve(registerBean); AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(1); assertThat(arguments.toArray()).hasSize(1);
assertThat((Map<String, String>) arguments.get(0)) assertThat((Map<String, String>) arguments.get(0))
.containsExactly(entry("one", "1"), entry("two", "2")); .containsExactly(entry("one", "1"), entry("two", "2"));
} }
@ParameterizedResolverTest(Sources.MAP_OF_BEANS) @ParameterizedResolverTest(Sources.MAP_OF_BEANS)
void resolveRequiredMapOfBeansInjectEmptySet(Source source) { void resolveArgumentsWithRequiredMapOfBeansInjectEmptySet(Source source) {
RegisteredBean registerBean = source.registerBean(this.beanFactory); RegisteredBean registerBean = source.registerBean(this.beanFactory);
AutowiredArguments arguments = source.getResolver().resolve(registerBean); AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(1); assertThat(arguments.toArray()).hasSize(1);
assertThat((Map<?, ?>) arguments.get(0)).isEmpty(); assertThat((Map<?, ?>) arguments.get(0)).isEmpty();
} }
@ParameterizedResolverTest(Sources.MULTI_ARGS) @ParameterizedResolverTest(Sources.MULTI_ARGS)
void resolveMultiArgsConstructor(Source source) { void resolveArgumentsWithMultiArgsConstructor(Source source) {
ResourceLoader resourceLoader = new DefaultResourceLoader(); ResourceLoader resourceLoader = new DefaultResourceLoader();
Environment environment = mock(Environment.class); Environment environment = mock(Environment.class);
this.beanFactory.registerResolvableDependency(ResourceLoader.class, this.beanFactory.registerResolvableDependency(ResourceLoader.class,
@ -345,7 +431,7 @@ class AutowiredInstantiationArgumentsResolverTests {
this.beanFactory.registerSingleton("environment", environment); this.beanFactory.registerSingleton("environment", environment);
this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("one", "1");
RegisteredBean registerBean = source.registerBean(this.beanFactory); RegisteredBean registerBean = source.registerBean(this.beanFactory);
AutowiredArguments arguments = source.getResolver().resolve(registerBean); AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(3); assertThat(arguments.toArray()).hasSize(3);
assertThat(arguments.getObject(0)).isEqualTo(resourceLoader); assertThat(arguments.getObject(0)).isEqualTo(resourceLoader);
assertThat(arguments.getObject(1)).isEqualTo(environment); assertThat(arguments.getObject(1)).isEqualTo(environment);
@ -354,7 +440,7 @@ class AutowiredInstantiationArgumentsResolverTests {
} }
@ParameterizedResolverTest(Sources.MIXED_ARGS) @ParameterizedResolverTest(Sources.MIXED_ARGS)
void resolveMixedArgsConstructorWithUserValue(Source source) { void resolveArgumentsWithMixedArgsConstructorWithUserValue(Source source) {
ResourceLoader resourceLoader = new DefaultResourceLoader(); ResourceLoader resourceLoader = new DefaultResourceLoader();
Environment environment = mock(Environment.class); Environment environment = mock(Environment.class);
this.beanFactory.registerResolvableDependency(ResourceLoader.class, this.beanFactory.registerResolvableDependency(ResourceLoader.class,
@ -367,7 +453,7 @@ class AutowiredInstantiationArgumentsResolverTests {
beanDefinition.getConstructorArgumentValues() beanDefinition.getConstructorArgumentValues()
.addIndexedArgumentValue(1, "user-value"); .addIndexedArgumentValue(1, "user-value");
}); });
AutowiredArguments arguments = source.getResolver().resolve(registerBean); AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(3); assertThat(arguments.toArray()).hasSize(3);
assertThat(arguments.getObject(0)).isEqualTo(resourceLoader); assertThat(arguments.getObject(0)).isEqualTo(resourceLoader);
assertThat(arguments.getObject(1)).isEqualTo("user-value"); assertThat(arguments.getObject(1)).isEqualTo("user-value");
@ -375,7 +461,7 @@ class AutowiredInstantiationArgumentsResolverTests {
} }
@ParameterizedResolverTest(Sources.MIXED_ARGS) @ParameterizedResolverTest(Sources.MIXED_ARGS)
void resolveMixedArgsConstructorWithUserBeanReference(Source source) { void resolveArgumentsWithMixedArgsConstructorWithUserBeanReference(Source source) {
ResourceLoader resourceLoader = new DefaultResourceLoader(); ResourceLoader resourceLoader = new DefaultResourceLoader();
Environment environment = mock(Environment.class); Environment environment = mock(Environment.class);
this.beanFactory.registerResolvableDependency(ResourceLoader.class, this.beanFactory.registerResolvableDependency(ResourceLoader.class,
@ -390,7 +476,7 @@ class AutowiredInstantiationArgumentsResolverTests {
beanDefinition.getConstructorArgumentValues() beanDefinition.getConstructorArgumentValues()
.addIndexedArgumentValue(1, new RuntimeBeanReference("two")); .addIndexedArgumentValue(1, new RuntimeBeanReference("two"));
}); });
AutowiredArguments arguments = source.getResolver().resolve(registerBean); AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(3); assertThat(arguments.toArray()).hasSize(3);
assertThat(arguments.getObject(0)).isEqualTo(resourceLoader); assertThat(arguments.getObject(0)).isEqualTo(resourceLoader);
assertThat(arguments.getObject(1)).isEqualTo("2"); assertThat(arguments.getObject(1)).isEqualTo("2");
@ -398,9 +484,9 @@ class AutowiredInstantiationArgumentsResolverTests {
} }
@Test @Test
void resolveUserValueWithTypeConversionRequired() { void resolveArgumentsWithUserValueWithTypeConversionRequired() {
Source source = new Source(CharDependency.class, Source source = new Source(CharDependency.class,
AutowiredInstantiationArgumentsResolver.forConstructor(char.class)); BeanInstanceSupplier.forConstructor(char.class));
RegisteredBean registerBean = source.registerBean(this.beanFactory, RegisteredBean registerBean = source.registerBean(this.beanFactory,
beanDefinition -> { beanDefinition -> {
beanDefinition beanDefinition
@ -408,37 +494,37 @@ class AutowiredInstantiationArgumentsResolverTests {
beanDefinition.getConstructorArgumentValues() beanDefinition.getConstructorArgumentValues()
.addIndexedArgumentValue(0, "\\"); .addIndexedArgumentValue(0, "\\");
}); });
AutowiredArguments arguments = source.getResolver().resolve(registerBean); AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(1); assertThat(arguments.toArray()).hasSize(1);
assertThat(arguments.getObject(0)).isInstanceOf(Character.class).isEqualTo('\\'); assertThat(arguments.getObject(0)).isInstanceOf(Character.class).isEqualTo('\\');
} }
@ParameterizedResolverTest(Sources.SINGLE_ARG) @ParameterizedResolverTest(Sources.SINGLE_ARG)
void resolveUserValueWithBeanReference(Source source) { void resolveArgumentsWithUserValueWithBeanReference(Source source) {
this.beanFactory.registerSingleton("stringBean", "string"); this.beanFactory.registerSingleton("stringBean", "string");
RegisteredBean registerBean = source.registerBean(this.beanFactory, RegisteredBean registerBean = source.registerBean(this.beanFactory,
beanDefinition -> beanDefinition.getConstructorArgumentValues() beanDefinition -> beanDefinition.getConstructorArgumentValues()
.addIndexedArgumentValue(0, .addIndexedArgumentValue(0,
new RuntimeBeanReference("stringBean"))); new RuntimeBeanReference("stringBean")));
AutowiredArguments arguments = source.getResolver().resolve(registerBean); AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(1); assertThat(arguments.toArray()).hasSize(1);
assertThat(arguments.getObject(0)).isEqualTo("string"); assertThat(arguments.getObject(0)).isEqualTo("string");
} }
@ParameterizedResolverTest(Sources.SINGLE_ARG) @ParameterizedResolverTest(Sources.SINGLE_ARG)
void resolveUserValueWithBeanDefinition(Source source) { void resolveArgumentsWithUserValueWithBeanDefinition(Source source) {
AbstractBeanDefinition userValue = BeanDefinitionBuilder AbstractBeanDefinition userValue = BeanDefinitionBuilder
.rootBeanDefinition(String.class, () -> "string").getBeanDefinition(); .rootBeanDefinition(String.class, () -> "string").getBeanDefinition();
RegisteredBean registerBean = source.registerBean(this.beanFactory, RegisteredBean registerBean = source.registerBean(this.beanFactory,
beanDefinition -> beanDefinition.getConstructorArgumentValues() beanDefinition -> beanDefinition.getConstructorArgumentValues()
.addIndexedArgumentValue(0, userValue)); .addIndexedArgumentValue(0, userValue));
AutowiredArguments arguments = source.getResolver().resolve(registerBean); AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(1); assertThat(arguments.toArray()).hasSize(1);
assertThat(arguments.getObject(0)).isEqualTo("string"); assertThat(arguments.getObject(0)).isEqualTo("string");
} }
@ParameterizedResolverTest(Sources.SINGLE_ARG) @ParameterizedResolverTest(Sources.SINGLE_ARG)
void resolveUserValueThatIsAlreadyResolved(Source source) { void resolveArgumentsWithUserValueThatIsAlreadyResolved(Source source) {
RegisteredBean registerBean = source.registerBean(this.beanFactory); RegisteredBean registerBean = source.registerBean(this.beanFactory);
BeanDefinition mergedBeanDefinition = this.beanFactory BeanDefinition mergedBeanDefinition = this.beanFactory
.getMergedBeanDefinition("testBean"); .getMergedBeanDefinition("testBean");
@ -446,13 +532,13 @@ class AutowiredInstantiationArgumentsResolverTests {
valueHolder.setConvertedValue("this is an a"); valueHolder.setConvertedValue("this is an a");
mergedBeanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, mergedBeanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
valueHolder); valueHolder);
AutowiredArguments arguments = source.getResolver().resolve(registerBean); AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(1); assertThat(arguments.toArray()).hasSize(1);
assertThat(arguments.getObject(0)).isEqualTo("this is an a"); assertThat(arguments.getObject(0)).isEqualTo("this is an a");
} }
@Test @Test
void resolveWhenUsingShortcutsInjectsDirectly() { void resolveArgumentsWhenUsingShortcutsInjectsDirectly() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory() { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory() {
@Override @Override
@ -462,35 +548,35 @@ class AutowiredInstantiationArgumentsResolverTests {
} }
}; };
AutowiredInstantiationArgumentsResolver resolver = AutowiredInstantiationArgumentsResolver BeanInstanceSupplier resolver = BeanInstanceSupplier
.forConstructor(String.class); .forConstructor(String.class);
Source source = new Source(String.class, resolver); Source source = new Source(String.class, resolver);
beanFactory.registerSingleton("one", "1"); beanFactory.registerSingleton("one", "1");
RegisteredBean registeredBean = source.registerBean(beanFactory); RegisteredBean registeredBean = source.registerBean(beanFactory);
assertThatExceptionOfType(AssertionError.class) assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> resolver.resolve(registeredBean)); .isThrownBy(() -> resolver.resolveArguments(registeredBean));
assertThat(resolver.withShortcuts("one").resolve(registeredBean).toArray()) assertThat(resolver.withShortcuts("one").resolveArguments(registeredBean).toArray())
.containsExactly("1"); .containsExactly("1");
} }
@Test @Test
void resolveRegistersDependantBeans() { void resolveArgumentsRegistersDependantBeans() {
AutowiredInstantiationArgumentsResolver resolver = AutowiredInstantiationArgumentsResolver BeanInstanceSupplier resolver = BeanInstanceSupplier
.forConstructor(String.class); .forConstructor(String.class);
Source source = new Source(SingleArgConstructor.class, resolver); Source source = new Source(SingleArgConstructor.class, resolver);
beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("one", "1");
RegisteredBean registeredBean = source.registerBean(this.beanFactory); RegisteredBean registeredBean = source.registerBean(this.beanFactory);
resolver.resolve(registeredBean); resolver.resolveArguments(registeredBean);
assertThat(this.beanFactory.getDependentBeans("one")).containsExactly("testBean"); assertThat(this.beanFactory.getDependentBeans("one")).containsExactly("testBean");
} }
/** /**
* Parameterized {@link Using} test backed by a {@link Sources}. * Parameterized test backed by a {@link Sources}.
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@ParameterizedTest @ParameterizedTest
@ArgumentsSource(SourcesArguments.class) @ArgumentsSource(SourcesArguments.class)
static @interface ParameterizedResolverTest { @interface ParameterizedResolverTest {
Sources value(); Sources value();
@ -510,8 +596,7 @@ class AutowiredInstantiationArgumentsResolverTests {
} }
@Override @Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
throws Exception {
return this.source.provideArguments(context); return this.source.provideArguments(context);
} }
@ -523,27 +608,25 @@ class AutowiredInstantiationArgumentsResolverTests {
enum Sources { enum Sources {
SINGLE_ARG { SINGLE_ARG {
@Override @Override
protected void setup() { protected void setup() {
add(SingleArgConstructor.class, AutowiredInstantiationArgumentsResolver add(SingleArgConstructor.class, BeanInstanceSupplier
.forConstructor(String.class)); .forConstructor(String.class));
add(String.class, add(String.class,
AutowiredInstantiationArgumentsResolver.forFactoryMethod( BeanInstanceSupplier.forFactoryMethod(
SingleArgFactory.class, "single", String.class)); SingleArgFactory.class, "single", String.class));
} }
}, },
INNER_CLASS_SINGLE_ARG { INNER_CLASS_SINGLE_ARG {
@Override @Override
protected void setup() { protected void setup() {
add(Enclosing.InnerSingleArgConstructor.class, add(Enclosing.InnerSingleArgConstructor.class,
AutowiredInstantiationArgumentsResolver BeanInstanceSupplier
.forConstructor(String.class)); .forConstructor(String.class));
add(String.class, add(String.class,
AutowiredInstantiationArgumentsResolver.forFactoryMethod( BeanInstanceSupplier.forFactoryMethod(
Enclosing.InnerSingleArgFactory.class, "single", Enclosing.InnerSingleArgFactory.class, "single",
String.class)); String.class));
} }
@ -551,71 +634,66 @@ class AutowiredInstantiationArgumentsResolverTests {
}, },
ARRAY_OF_BEANS { ARRAY_OF_BEANS {
@Override @Override
protected void setup() { protected void setup() {
add(BeansCollectionConstructor.class, add(BeansCollectionConstructor.class,
AutowiredInstantiationArgumentsResolver BeanInstanceSupplier
.forConstructor(String[].class)); .forConstructor(String[].class));
add(String.class, add(String.class,
AutowiredInstantiationArgumentsResolver.forFactoryMethod( BeanInstanceSupplier.forFactoryMethod(
BeansCollectionFactory.class, "array", String[].class)); BeansCollectionFactory.class, "array", String[].class));
} }
}, },
LIST_OF_BEANS { LIST_OF_BEANS {
@Override @Override
protected void setup() { protected void setup() {
add(BeansCollectionConstructor.class, add(BeansCollectionConstructor.class,
AutowiredInstantiationArgumentsResolver BeanInstanceSupplier
.forConstructor(List.class)); .forConstructor(List.class));
add(String.class, add(String.class,
AutowiredInstantiationArgumentsResolver.forFactoryMethod( BeanInstanceSupplier.forFactoryMethod(
BeansCollectionFactory.class, "list", List.class)); BeansCollectionFactory.class, "list", List.class));
} }
}, },
SET_OF_BEANS { SET_OF_BEANS {
@Override @Override
protected void setup() { protected void setup() {
add(BeansCollectionConstructor.class, add(BeansCollectionConstructor.class,
AutowiredInstantiationArgumentsResolver BeanInstanceSupplier
.forConstructor(Set.class)); .forConstructor(Set.class));
add(String.class, add(String.class,
AutowiredInstantiationArgumentsResolver.forFactoryMethod( BeanInstanceSupplier.forFactoryMethod(
BeansCollectionFactory.class, "set", Set.class)); BeansCollectionFactory.class, "set", Set.class));
} }
}, },
MAP_OF_BEANS { MAP_OF_BEANS {
@Override @Override
protected void setup() { protected void setup() {
add(BeansCollectionConstructor.class, add(BeansCollectionConstructor.class,
AutowiredInstantiationArgumentsResolver BeanInstanceSupplier
.forConstructor(Map.class)); .forConstructor(Map.class));
add(String.class, add(String.class,
AutowiredInstantiationArgumentsResolver.forFactoryMethod( BeanInstanceSupplier.forFactoryMethod(
BeansCollectionFactory.class, "map", Map.class)); BeansCollectionFactory.class, "map", Map.class));
} }
}, },
MULTI_ARGS { MULTI_ARGS {
@Override @Override
protected void setup() { protected void setup() {
add(MultiArgsConstructor.class, add(MultiArgsConstructor.class,
AutowiredInstantiationArgumentsResolver.forConstructor( BeanInstanceSupplier.forConstructor(
ResourceLoader.class, Environment.class, ResourceLoader.class, Environment.class,
ObjectProvider.class)); ObjectProvider.class));
add(String.class, add(String.class,
AutowiredInstantiationArgumentsResolver.forFactoryMethod( BeanInstanceSupplier.forFactoryMethod(
MultiArgsFactory.class, "multiArgs", ResourceLoader.class, MultiArgsFactory.class, "multiArgs", ResourceLoader.class,
Environment.class, ObjectProvider.class)); Environment.class, ObjectProvider.class));
} }
@ -623,14 +701,13 @@ class AutowiredInstantiationArgumentsResolverTests {
}, },
MIXED_ARGS { MIXED_ARGS {
@Override @Override
protected void setup() { protected void setup() {
add(MixedArgsConstructor.class, add(MixedArgsConstructor.class,
AutowiredInstantiationArgumentsResolver.forConstructor( BeanInstanceSupplier.forConstructor(
ResourceLoader.class, String.class, Environment.class)); ResourceLoader.class, String.class, Environment.class));
add(String.class, add(String.class,
AutowiredInstantiationArgumentsResolver.forFactoryMethod( BeanInstanceSupplier.forFactoryMethod(
MixedArgsFactory.class, "mixedArgs", ResourceLoader.class, MixedArgsFactory.class, "mixedArgs", ResourceLoader.class,
String.class, Environment.class)); String.class, Environment.class));
} }
@ -639,7 +716,7 @@ class AutowiredInstantiationArgumentsResolverTests {
private final List<Arguments> arguments; private final List<Arguments> arguments;
private Sources() { Sources() {
this.arguments = new ArrayList<>(); this.arguments = new ArrayList<>();
setup(); setup();
} }
@ -647,7 +724,7 @@ class AutowiredInstantiationArgumentsResolverTests {
protected abstract void setup(); protected abstract void setup();
protected final void add(Class<?> beanClass, protected final void add(Class<?> beanClass,
AutowiredInstantiationArgumentsResolver resolver) { BeanInstanceSupplier resolver) {
this.arguments.add(Arguments.of(new Source(beanClass, resolver))); this.arguments.add(Arguments.of(new Source(beanClass, resolver)));
} }
@ -657,16 +734,12 @@ class AutowiredInstantiationArgumentsResolverTests {
} }
static class Source { static class BeanRegistrar {
private final Class<?> beanClass; final Class<?> beanClass;
private final AutowiredInstantiationArgumentsResolver resolver; public BeanRegistrar(Class<?> beanClass) {
public Source(Class<?> beanClass,
AutowiredInstantiationArgumentsResolver resolver) {
this.beanClass = beanClass; this.beanClass = beanClass;
this.resolver = resolver;
} }
RegisteredBean registerBean(DefaultListableBeanFactory beanFactory) { RegisteredBean registerBean(DefaultListableBeanFactory beanFactory) {
@ -685,8 +758,19 @@ class AutowiredInstantiationArgumentsResolverTests {
beanFactory.registerBeanDefinition(beanName, beanDefinition); beanFactory.registerBeanDefinition(beanName, beanDefinition);
return RegisteredBean.of(beanFactory, beanName); return RegisteredBean.of(beanFactory, beanName);
} }
}
AutowiredInstantiationArgumentsResolver getResolver() { static class Source extends BeanRegistrar {
private final BeanInstanceSupplier resolver;
public Source(Class<?> beanClass,
BeanInstanceSupplier resolver) {
super(beanClass);
this.resolver = resolver;
}
BeanInstanceSupplier getResolver() {
return this.resolver; return this.resolver;
} }
@ -715,7 +799,7 @@ class AutowiredInstantiationArgumentsResolverTests {
} }
String getString() { String getString() {
return string; return this.string;
} }
} }
@ -834,7 +918,7 @@ class AutowiredInstantiationArgumentsResolverTests {
} }
static interface MethodOnInterface { interface MethodOnInterface {
default String test() { default String test() {
return "Test"; return "Test";

View File

@ -172,7 +172,7 @@ class InstanceSupplierCodeGeneratorTests {
instanceSupplier); instanceSupplier);
assertThat(bean).isInstanceOf(TestBeanWithPrivateConstructor.class); assertThat(bean).isInstanceOf(TestBeanWithPrivateConstructor.class);
assertThat(compiled.getSourceFile()) assertThat(compiled.getSourceFile())
.contains("resolveAndInstantiate(registeredBean)"); .contains("return BeanInstanceSupplier.forConstructor();");
}); });
assertThat(getReflectionHints().getTypeHint(TestBeanWithPrivateConstructor.class)) assertThat(getReflectionHints().getTypeHint(TestBeanWithPrivateConstructor.class))
.satisfies(hasConstructorWithMode(ExecutableMode.INVOKE)); .satisfies(hasConstructorWithMode(ExecutableMode.INVOKE));
@ -211,7 +211,8 @@ class InstanceSupplierCodeGeneratorTests {
assertThat(bean).isInstanceOf(String.class); assertThat(bean).isInstanceOf(String.class);
assertThat(bean).isEqualTo("Hello"); assertThat(bean).isEqualTo("Hello");
assertThat(compiled.getSourceFile()) assertThat(compiled.getSourceFile())
.contains("resolveAndInstantiate(registeredBean)"); .contains("BeanInstanceSupplier.forFactoryMethod")
.doesNotContain("withGenerator");
}); });
assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class)) assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class))
.satisfies(hasMethodWithMode(ExecutableMode.INVOKE)); .satisfies(hasMethodWithMode(ExecutableMode.INVOKE));
@ -271,7 +272,7 @@ class InstanceSupplierCodeGeneratorTests {
Integer bean = getBean(beanFactory, beanDefinition, instanceSupplier); Integer bean = getBean(beanFactory, beanDefinition, instanceSupplier);
assertThat(bean).isInstanceOf(Integer.class); assertThat(bean).isInstanceOf(Integer.class);
assertThat(bean).isEqualTo(42); assertThat(bean).isEqualTo(42);
assertThat(compiled.getSourceFile()).contains(") throws Exception {"); assertThat(compiled.getSourceFile()).doesNotContain(") throws Exception {");
}); });
assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class)) assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class))
.satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT)); .satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT));

View File

@ -0,0 +1,60 @@
/*
* Copyright 2002-2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.support;
import java.lang.reflect.Method;
import org.junit.jupiter.api.Test;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link RootBeanDefinition}.
*
* @author Stephane Nicoll
*/
class RootBeanDefinitionTests {
@Test
void setInstanceSetResolvedFactoryMethod() {
InstanceSupplier<?> instanceSupplier = mock(InstanceSupplier.class);
Method method = ReflectionUtils.findMethod(String.class, "toString");
given(instanceSupplier.getFactoryMethod()).willReturn(method);
RootBeanDefinition beanDefinition = new RootBeanDefinition(String.class);
beanDefinition.setInstanceSupplier(instanceSupplier);
assertThat(beanDefinition.getResolvedFactoryMethod()).isEqualTo(method);
verify(instanceSupplier).getFactoryMethod();
}
@Test
void setInstanceDoesNotOverrideResolvedFactoryMethodWithNull() {
InstanceSupplier<?> instanceSupplier = mock(InstanceSupplier.class);
given(instanceSupplier.getFactoryMethod()).willReturn(null);
Method method = ReflectionUtils.findMethod(String.class, "toString");
RootBeanDefinition beanDefinition = new RootBeanDefinition(String.class);
beanDefinition.setResolvedFactoryMethod(method);
beanDefinition.setInstanceSupplier(instanceSupplier);
assertThat(beanDefinition.getResolvedFactoryMethod()).isEqualTo(method);
verify(instanceSupplier).getFactoryMethod();
}
}