From d93e5eec68eae1678e617f7d063a026650d3ae5a Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 18 Jul 2022 09:20:26 +0100 Subject: [PATCH] Link factoryMethod consistently in AOT-generated bean definitions See gh-28748 --- .../beans/factory/aot/AutowiredArguments.java | 2 +- .../aot/AutowiredMethodArgumentsResolver.java | 2 +- ...esolver.java => BeanInstanceSupplier.java} | 187 ++++++---- .../aot/InstanceSupplierCodeGenerator.java | 203 +++++------ .../factory/support/InstanceSupplier.java | 14 + .../factory/support/RootBeanDefinition.java | 11 + ...ts.java => BeanInstanceSupplierTests.java} | 330 +++++++++++------- .../InstanceSupplierCodeGeneratorTests.java | 7 +- .../support/RootBeanDefinitionTests.java | 60 ++++ 9 files changed, 499 insertions(+), 317 deletions(-) rename spring-beans/src/main/java/org/springframework/beans/factory/aot/{AutowiredInstantiationArgumentsResolver.java => BeanInstanceSupplier.java} (74%) rename spring-beans/src/test/java/org/springframework/beans/factory/aot/{AutowiredInstantiationArgumentsResolverTests.java => BeanInstanceSupplierTests.java} (68%) create mode 100644 spring-beans/src/test/java/org/springframework/beans/factory/support/RootBeanDefinitionTests.java diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredArguments.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredArguments.java index 14178b512d97..f4c090647919 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredArguments.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredArguments.java @@ -26,7 +26,7 @@ * @author Phillip Webb * @author Stephane Nicoll * @since 6.0 - * @see AutowiredInstantiationArgumentsResolver + * @see BeanInstanceSupplier * @see AutowiredMethodArgumentsResolver */ @FunctionalInterface diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredMethodArgumentsResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredMethodArgumentsResolver.java index 681297eb3470..dea4d1922674 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredMethodArgumentsResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredMethodArgumentsResolver.java @@ -105,7 +105,7 @@ public static AutowiredMethodArgumentsResolver forRequiredMethod(String methodNa } /** - * Return a new {@link AutowiredInstantiationArgumentsResolver} instance + * Return a new {@link AutowiredMethodArgumentsResolver} instance * that uses direct bean name injection shortcuts for specific parameters. * @param beanNames the bean names to use as shortcuts (aligned with the * method parameters) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredInstantiationArgumentsResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java similarity index 74% rename from spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredInstantiationArgumentsResolver.java rename to spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java index cc8be0706a33..5d8144c73d7b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredInstantiationArgumentsResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java @@ -40,6 +40,7 @@ import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory; 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.RootBeanDefinition; import org.springframework.core.CollectionFactory; @@ -49,67 +50,81 @@ import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; +import org.springframework.util.function.ThrowingBiFunction; import org.springframework.util.function.ThrowingFunction; +import org.springframework.util.function.ThrowingSupplier; /** - * Resolver used to support the autowiring of constructors or factory methods. - * Typically used in AOT-processed applications as a targeted alternative to the - * reflection based injection. + * Specialized {@link InstanceSupplier} that provides the factory {@link Method} + * used to instantiate the underlying bean instance, if any. Transparently + * handles resolution of {@link AutowiredArguments} if necessary. Typically used + * in AOT-processed applications as a targeted alternative to the reflection + * based injection. *

- * When resolving arguments in a native image, the {@link Constructor} or - * {@link Method} being used must be marked with an - * {@link ExecutableMode#INTROSPECT introspection} hint so that parameter - * annotations can be read. Full {@link ExecutableMode#INVOKE invocation} hints - * are only required if the {@code resolveAndInstantiate} methods of this class - * are being used (typically to support private constructors, methods or - * classes). + * If no {@code generator} is provided, reflection is used to instantiate the + * bean instance, and full {@link ExecutableMode#INVOKE invocation} hints are + * contributed. Multiple generator callback styles are supported: + *

+ * Generator callbacks handle checked exceptions so that the caller does not + * have to deal with it. * * @author Phillip Webb * @author Stephane Nicoll * @since 6.0 * @see AutowiredArguments */ -public final class AutowiredInstantiationArgumentsResolver extends AutowiredElementResolver { +public final class BeanInstanceSupplier extends AutowiredElementResolver implements InstanceSupplier { private final ExecutableLookup lookup; + @Nullable + private final ThrowingBiFunction generator; + @Nullable private final String[] shortcuts; - private AutowiredInstantiationArgumentsResolver(ExecutableLookup lookup, + private BeanInstanceSupplier(ExecutableLookup lookup, + @Nullable ThrowingBiFunction generator, @Nullable String[] shortcuts) { - this.lookup = lookup; + this.generator = generator; this.shortcuts = shortcuts; } - /** - * Create a {@link AutowiredInstantiationArgumentsResolver} that resolves + * Create a {@link BeanInstanceSupplier} that resolves * arguments for the specified bean constructor. * @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) { Assert.notNull(parameterTypes, "'parameterTypes' must not be null"); Assert.noNullElements(parameterTypes, "'parameterTypes' must not contain null elements"); - return new AutowiredInstantiationArgumentsResolver( - new ConstructorLookup(parameterTypes), null); + return new BeanInstanceSupplier( + new ConstructorLookup(parameterTypes), null, null); } /** - * Create a new {@link AutowiredInstantiationArgumentsResolver} that + * Create a new {@link BeanInstanceSupplier} that * resolves arguments for the specified factory method. * @param declaringClass the class that declares the factory method * @param methodName the factory method name * @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) { Assert.notNull(declaringClass, "'declaringClass' must not be null"); @@ -117,9 +132,9 @@ public static AutowiredInstantiationArgumentsResolver forFactoryMethod( Assert.notNull(parameterTypes, "'parameterTypes' must not be null"); Assert.noNullElements(parameterTypes, "'parameterTypes' must not contain null elements"); - return new AutowiredInstantiationArgumentsResolver( + return new BeanInstanceSupplier( new FactoryMethodLookup(declaringClass, methodName, parameterTypes), - null); + null, null); } @@ -128,74 +143,92 @@ ExecutableLookup getLookup() { } /** - * Return a new {@link AutowiredInstantiationArgumentsResolver} 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 AutowiredInstantiationArgumentsResolver} instance - * that uses the shortcuts + * Return a new {@link BeanInstanceSupplier} instance that uses the specified + * {@code generator} bi-function to instantiate the underlying bean. + * @param generator a {@link ThrowingBiFunction} that uses the + * {@link RegisteredBean} and resolved {@link AutowiredArguments} to + * instantiate the underlying bean + * @return a new {@link BeanInstanceSupplier} instance with the specified + * generator */ - public AutowiredInstantiationArgumentsResolver withShortcuts(String... beanNames) { - return new AutowiredInstantiationArgumentsResolver(this.lookup, beanNames); + public BeanInstanceSupplier withGenerator( + ThrowingBiFunction 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 - * the given generator in order to return a result. - * @param registeredBean the registered bean - * @param generator the generator to execute with the resolved constructor - * or factory method arguments + * Return a new {@link BeanInstanceSupplier} instance that uses the specified + * {@code generator} function to instantiate the underlying bean. + * @param generator a {@link ThrowingFunction} that uses the + * {@link RegisteredBean} to instantiate the underlying bean + * @return a new {@link BeanInstanceSupplier} instance with the specified + * generator */ - public T resolve(RegisteredBean registeredBean, - ThrowingFunction generator) { - - Assert.notNull(registeredBean, "'registeredBean' must not be null"); - Assert.notNull(generator, "'action' must not be null"); - AutowiredArguments resolved = resolveArguments(registeredBean, - this.lookup.get(registeredBean)); - return generator.apply(resolved); + public BeanInstanceSupplier withGenerator( + ThrowingFunction generator) { + Assert.notNull(generator, "'generator' must not be null"); + return new BeanInstanceSupplier(this.lookup, (registeredBean, args) -> + generator.apply(registeredBean), this.shortcuts); } /** - * Resolve arguments for the specified registered bean. - * @param registeredBean the registered bean - * @return the resolved constructor or factory method arguments + * 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 AutowiredArguments resolve(RegisteredBean registeredBean) { - Assert.notNull(registeredBean, "'registeredBean' must not be null"); - return resolveArguments(registeredBean, this.lookup.get(registeredBean)); + public BeanInstanceSupplier withGenerator(ThrowingSupplier generator) { + Assert.notNull(generator, "'generator' must not be null"); + return new BeanInstanceSupplier(this.lookup, (registeredBean, args) -> + generator.get(), this.shortcuts); } /** - * 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 + * 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 */ - @SuppressWarnings("unchecked") - public T resolveAndInstantiate(RegisteredBean registeredBean) { - return (T) resolveAndInstantiate(registeredBean, Object.class); + 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"); + Executable executable = this.lookup.get(registeredBean); + AutowiredArguments arguments = resolveArguments(registeredBean, executable); + if (this.generator != null) { + 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; } /** - * Resolve arguments for the specified registered bean and instantiate a new - * instance using reflection. + * Resolve arguments for the specified registered bean. * @param registeredBean the registered bean - * @param requiredType the required result type - * @return an instance of the bean + * @return the resolved constructor or factory method arguments */ - @SuppressWarnings("unchecked") - public T resolveAndInstantiate(RegisteredBean registeredBean, - Class requiredType) { - + AutowiredArguments resolveArguments(RegisteredBean registeredBean) { 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; + return resolveArguments(registeredBean, this.lookup.get(registeredBean)); } private AutowiredArguments resolveArguments(RegisteredBean registeredBean, @@ -233,9 +266,6 @@ private AutowiredArguments resolveArguments(RegisteredBean registeredBean, autowiredBeans, parameter, dependencyDescriptor, argumentValue); } registerDependentBeans(beanFactory, beanName, autowiredBeans); - if (executable instanceof Method method) { - mergedBeanDefinition.setResolvedFactoryMethod(method); - } return AutowiredArguments.of(resolved); } @@ -450,9 +480,12 @@ private static class FactoryMethodLookup extends ExecutableLookup { this.parameterTypes = parameterTypes; } - @Override public Executable get(RegisteredBean registeredBean) { + return get(); + } + + Method get() { Method method = ReflectionUtils.findMethod(this.declaringClass, this.methodName, this.parameterTypes); Assert.notNull(method, () -> String.format("%s cannot be found", this)); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java index 8ec4ad93e863..6ec5b6e4dfef 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java @@ -36,16 +36,20 @@ import org.springframework.javapoet.ClassName; import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.MethodSpec; -import org.springframework.javapoet.MethodSpec.Builder; import org.springframework.util.ClassUtils; 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. *

- * Generates code in the form:

{@code
- * InstanceSupplier.of(TheGeneratedClass::getMyBeanInstance);
- * }
+ * Generated code is usually a method reference that generate the + * {@link BeanInstanceSupplier}, but some shortcut can be used as well such + * as: + *
+ * {@code InstanceSupplier.of(TheGeneratedClass::getMyBeanInstance);}
+ * 
* * @author Phillip Webb * @author Stephane Nicoll @@ -55,6 +59,8 @@ class InstanceSupplierCodeGenerator { 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 = { javax.lang.model.element.Modifier.PRIVATE, javax.lang.model.element.Modifier.STATIC }; @@ -109,15 +115,14 @@ private CodeBlock generateCodeForConstructor(RegisteredBean registeredBean, constructor); if (accessVisibility == AccessVisibility.PUBLIC || accessVisibility == AccessVisibility.PACKAGE_PRIVATE) { - return generateCodeForAccessibleConstructor(name, constructor, declaringClass, - dependsOnBean); + return generateCodeForAccessibleConstructor(name, constructor, dependsOnBean, + declaringClass); } - return generateCodeForInaccessibleConstructor(name, constructor, declaringClass, - dependsOnBean); + return generateCodeForInaccessibleConstructor(name, constructor, dependsOnBean); } private CodeBlock generateCodeForAccessibleConstructor(String name, - Constructor constructor, Class declaringClass, boolean dependsOnBean) { + Constructor constructor, boolean dependsOnBean, Class declaringClass) { this.generationContext.getRuntimeHints().reflection() .registerConstructor(constructor, INTROSPECT); @@ -132,60 +137,49 @@ private CodeBlock generateCodeForAccessibleConstructor(String name, return CodeBlock.of("$T.of($T::new)", ThrowingSupplier.class, declaringClass); } - GeneratedMethod generatedMethod = generateGetInstanceMethod(method -> - buildGetInstanceMethodForConstructor(method, name, constructor, declaringClass, - dependsOnBean, PRIVATE_STATIC)); - return CodeBlock.of("$T.of($T::$L)", InstanceSupplier.class, this.className, - generatedMethod.getName()); + GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method -> + buildGetInstanceMethodForConstructor(method, name, constructor, + declaringClass, dependsOnBean, PRIVATE_STATIC)); + return generateReturnStatement(generatedMethod); } private CodeBlock generateCodeForInaccessibleConstructor(String name, - Constructor constructor, Class declaringClass, boolean dependsOnBean) { + Constructor constructor, boolean dependsOnBean) { this.generationContext.getRuntimeHints().reflection() .registerConstructor(constructor); - GeneratedMethod generatedMethod = generateGetInstanceMethod(method -> { - method.addJavadoc("Instantiate the bean instance for '$L'.", name); + GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method -> { + method.addJavadoc("Get the bean instance supplier for '$L'.", name); method.addModifiers(PRIVATE_STATIC); - method.returns(declaringClass); - method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER_NAME); + method.returns(BeanInstanceSupplier.class); int parameterOffset = (!dependsOnBean) ? 0 : 1; method.addStatement( generateResolverForConstructor(constructor, parameterOffset)); - method.addStatement("return resolver.resolveAndInstantiate($L)", - REGISTERED_BEAN_PARAMETER_NAME); }); - return CodeBlock.of("$T.of($T::$L)", InstanceSupplier.class, this.className, - generatedMethod.getName()); + return generateReturnStatement(generatedMethod); } private void buildGetInstanceMethodForConstructor(MethodSpec.Builder method, String name, Constructor constructor, Class declaringClass, 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.returns(declaringClass); - method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER_NAME); - if (constructor.getParameterCount() == 0) { - CodeBlock instantiationCode = generateNewInstanceCodeForConstructor( - dependsOnBean, declaringClass, NO_ARGS); - method.addCode(generateReturnStatement(instantiationCode)); - } - else { - int parameterOffset = (!dependsOnBean) ? 0 : 1; - CodeBlock.Builder code = CodeBlock.builder(); - code.addStatement( - generateResolverForConstructor(constructor, parameterOffset)); - CodeBlock arguments = new AutowiredArgumentsCodeGenerator(declaringClass, - 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()); - } + method.returns(BeanInstanceSupplier.class); + int parameterOffset = (!dependsOnBean) ? 0 : 1; + CodeBlock.Builder code = CodeBlock.builder(); + code.add(generateResolverForConstructor(constructor, parameterOffset)); + boolean hasArguments = constructor.getParameterCount() > 0; + CodeBlock arguments = (hasArguments ? new AutowiredArgumentsCodeGenerator(declaringClass, + constructor).generateCode(constructor.getParameterTypes(), parameterOffset) + : NO_ARGS); + CodeBlock newInstance = generateNewInstanceCodeForConstructor(dependsOnBean, + declaringClass, arguments); + CodeBlock lambda = generateGeneratorLambdaArguments(hasArguments); + code.add("\n").indent().indent() + .addStatement(".withGenerator($L -> $L)", lambda, newInstance) + .unindent().unindent(); + method.addCode(code.build()); } private CodeBlock generateResolverForConstructor(Constructor constructor, @@ -193,9 +187,8 @@ private CodeBlock generateResolverForConstructor(Constructor constructor, CodeBlock parameterTypes = generateParameterTypesCode( constructor.getParameterTypes(), parameterOffset); - return CodeBlock.of("$T resolver = $T.forConstructor($L)", - AutowiredInstantiationArgumentsResolver.class, - AutowiredInstantiationArgumentsResolver.class, parameterTypes); + return CodeBlock.of("return $T.forConstructor($L)", + BeanInstanceSupplier.class, parameterTypes); } private CodeBlock generateNewInstanceCodeForConstructor(boolean dependsOnBean, @@ -232,21 +225,16 @@ private CodeBlock generateCodeForAccessibleFactoryMethod(String name, this.generationContext.getRuntimeHints().reflection() .registerMethod(factoryMethod, INTROSPECT); if (!dependsOnBean && factoryMethod.getParameterCount() == 0) { - if (!this.allowDirectSupplierShortcut) { - return CodeBlock.of("$T.using($T::$L)", InstanceSupplier.class, - declaringClass, factoryMethod.getName()); - } - if (!isThrowingCheckedException(factoryMethod)) { - return CodeBlock.of("$T::$L", declaringClass, factoryMethod.getName()); - } - return CodeBlock.of("$T.of($T::$L)", ThrowingSupplier.class, declaringClass, - factoryMethod.getName()); + CodeBlock.Builder code = CodeBlock.builder(); + code.add("$T.forFactoryMethod($T.class, $S)", BeanInstanceSupplier.class, + declaringClass, factoryMethod.getName()); + code.add(".withGenerator($T::$L)", declaringClass, factoryMethod.getName()); + return code.build(); } - GeneratedMethod generatedMethod = generateGetInstanceMethod(method -> - buildGetInstanceMethodForFactoryMethod(method, name, factoryMethod, declaringClass, - dependsOnBean, PRIVATE_STATIC)); - return CodeBlock.of("$T.of($T::$L)", InstanceSupplier.class, this.className, - generatedMethod.getName()); + GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method -> + buildGetInstanceMethodForFactoryMethod(method, name, factoryMethod, + declaringClass, dependsOnBean, PRIVATE_STATIC)); + return generateReturnStatement(getInstanceMethod); } private CodeBlock generateCodeForInaccessibleFactoryMethod(String name, @@ -254,18 +242,14 @@ private CodeBlock generateCodeForInaccessibleFactoryMethod(String name, this.generationContext.getRuntimeHints().reflection() .registerMethod(factoryMethod); - GeneratedMethod generatedMethod = generateGetInstanceMethod(method -> { - method.addJavadoc("Instantiate the bean instance for '$L'.", name); + GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method -> { + method.addJavadoc("Get the bean instance supplier for '$L'.", name); method.addModifiers(PRIVATE_STATIC); - method.returns(factoryMethod.getReturnType()); - method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER_NAME); - method.addStatement(generateResolverForFactoryMethod(factoryMethod, + method.returns(BeanInstanceSupplier.class); + method.addStatement(generateInstanceSupplierForFactoryMethod(factoryMethod, declaringClass, factoryMethod.getName())); - method.addStatement("return resolver.resolveAndInstantiate($L)", - REGISTERED_BEAN_PARAMETER_NAME); }); - return CodeBlock.of("$T.of($T::$L)", InstanceSupplier.class, this.className, - generatedMethod.getName()); + return generateReturnStatement(getInstanceMethod); } private void buildGetInstanceMethodForFactoryMethod(MethodSpec.Builder method, @@ -273,46 +257,37 @@ private void buildGetInstanceMethodForFactoryMethod(MethodSpec.Builder method, boolean dependsOnBean, javax.lang.model.element.Modifier... modifiers) { 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.returns(factoryMethod.getReturnType()); - if (isThrowingCheckedException(factoryMethod)) { - method.addException(Exception.class); - } - method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER_NAME); - if (factoryMethod.getParameterCount() == 0) { - CodeBlock instantiationCode = generateNewInstanceCodeForMethod(dependsOnBean, - declaringClass, factoryMethodName, NO_ARGS); - method.addCode(generateReturnStatement(instantiationCode)); - } - else { - CodeBlock.Builder code = CodeBlock.builder(); - 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()); - } + method.returns(BeanInstanceSupplier.class); + CodeBlock.Builder code = CodeBlock.builder(); + boolean hasArguments = factoryMethod.getParameterCount() > 0; + CodeBlock arguments = hasArguments + ? new AutowiredArgumentsCodeGenerator(declaringClass, + factoryMethod).generateCode(factoryMethod.getParameterTypes()) : NO_ARGS; + CodeBlock newInstance = generateNewInstanceCodeForMethod(dependsOnBean, + declaringClass, factoryMethodName, arguments); + CodeBlock instanceSupplier = generateInstanceSupplierForFactoryMethod(factoryMethod, + declaringClass, factoryMethodName); + CodeBlock lambda = generateGeneratorLambdaArguments(hasArguments); + code.add(instanceSupplier).add("\n").indent().indent() + .add(".withGenerator($L -> $L)", lambda, newInstance) + .unindent().unindent(); + method.addStatement(code.build()); } - private CodeBlock generateResolverForFactoryMethod(Method factoryMethod, + private CodeBlock generateInstanceSupplierForFactoryMethod(Method factoryMethod, Class declaringClass, String factoryMethodName) { if (factoryMethod.getParameterCount() == 0) { - return CodeBlock.of("$T resolver = $T.forFactoryMethod($T.class, $S)", - AutowiredInstantiationArgumentsResolver.class, - AutowiredInstantiationArgumentsResolver.class, declaringClass, + return CodeBlock.of("return $T.forFactoryMethod($T.class, $S)", + BeanInstanceSupplier.class, declaringClass, factoryMethodName); } CodeBlock parameterTypes = generateParameterTypesCode( factoryMethod.getParameterTypes(), 0); - return CodeBlock.of("$T resolver = $T.forFactoryMethod($T.class, $S, $L)", - AutowiredInstantiationArgumentsResolver.class, - AutowiredInstantiationArgumentsResolver.class, declaringClass, + return CodeBlock.of("return $T.forFactoryMethod($T.class, $S, $L)", + BeanInstanceSupplier.class, declaringClass, factoryMethodName, parameterTypes); } @@ -326,10 +301,14 @@ private CodeBlock generateNewInstanceCodeForMethod(boolean dependsOnBean, REGISTERED_BEAN_PARAMETER_NAME, declaringClass, factoryMethodName, args); } - private CodeBlock generateReturnStatement(CodeBlock instantiationCode) { - CodeBlock.Builder code = CodeBlock.builder(); - code.addStatement("return $L", instantiationCode); - return code.build(); + private CodeBlock generateReturnStatement(GeneratedMethod getInstanceMethod) { + return CodeBlock.of("$T.$L()", this.className, getInstanceMethod.getName()); + } + + private CodeBlock generateGeneratorLambdaArguments(boolean withArguments) { + return (withArguments + ? CodeBlock.of("($L, $L)", REGISTERED_BEAN_PARAMETER_NAME, ARGS_PARAMETER_NAME) + : CodeBlock.of("($L)", REGISTERED_BEAN_PARAMETER_NAME)); } protected AccessVisibility getAccessVisibility(RegisteredBean registeredBean, @@ -342,16 +321,16 @@ protected AccessVisibility getAccessVisibility(RegisteredBean registeredBean, } 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++) { - builder.add(i != offset ? ", " : ""); - builder.add("$T.class", parameterTypes[i]); + code.add(i != offset ? ", " : ""); + code.add("$T.class", parameterTypes[i]); } - return builder.build(); + return code.build(); } - private GeneratedMethod generateGetInstanceMethod(Consumer method) { - return this.generatedMethods.add("getInstance", method); + private GeneratedMethod generateGetInstanceSupplierMethod(Consumer method) { + return this.generatedMethods.add("getInstanceSupplier", method); } private boolean isThrowingCheckedException(Executable executable) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/InstanceSupplier.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/InstanceSupplier.java index 3eede1f08844..9b7dbfcb8ec6 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/InstanceSupplier.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/InstanceSupplier.java @@ -16,8 +16,10 @@ package org.springframework.beans.factory.support; +import java.lang.reflect.Method; import java.util.function.Supplier; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.function.ThrowingBiFunction; import org.springframework.util.function.ThrowingSupplier; @@ -29,6 +31,7 @@ * supply the instance. * * @author Phillip Webb + * @author Stephane Nicoll * @since 6.0 * @param the type of instance supplied by this supplier * @see RegisteredBean @@ -49,6 +52,17 @@ default T getWithException() { */ 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 * this supplier, and then applied the {@code after} function to obtain the diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java index 0bcd497127a4..fb67bb041064 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -429,6 +429,17 @@ public Method getResolvedFactoryMethod() { return this.factoryMethodToIntrospect; } + @Override + public void setInstanceSupplier(@Nullable Supplier instanceSupplier) { + super.setInstanceSupplier(instanceSupplier); + if (instanceSupplier instanceof InstanceSupplier specificSupplier) { + Method factoryMethod = specificSupplier.getFactoryMethod(); + if (factoryMethod != null) { + setResolvedFactoryMethod(factoryMethod); + } + } + } + /** * Register an externally managed configuration method or field. */ diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/AutowiredInstantiationArgumentsResolverTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanInstanceSupplierTests.java similarity index 68% rename from spring-beans/src/test/java/org/springframework/beans/factory/aot/AutowiredInstantiationArgumentsResolverTests.java rename to spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanInstanceSupplierTests.java index eedb7200508c..fa3ae93ca6f4 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/AutowiredInstantiationArgumentsResolverTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanInstanceSupplierTests.java @@ -20,6 +20,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Executable; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -39,7 +40,7 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.UnsatisfiedDependencyException; 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.ConstructorArgumentValues.ValueHolder; import org.springframework.beans.factory.config.DependencyDescriptor; @@ -54,6 +55,10 @@ import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; 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.assertThatExceptionOfType; @@ -62,19 +67,19 @@ import static org.mockito.Mockito.mock; /** - * Tests for {@link AutowiredInstantiationArgumentsResolver}. + * Tests for {@link BeanInstanceSupplier}. * * @author Phillip Webb * @author Stephane Nicoll */ -class AutowiredInstantiationArgumentsResolverTests { +class BeanInstanceSupplierTests { private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); @Test void forConstructorWhenParameterTypesIsNullThrowsException() { assertThatIllegalArgumentException() - .isThrownBy(() -> AutowiredInstantiationArgumentsResolver + .isThrownBy(() -> BeanInstanceSupplier .forConstructor((Class[]) null)) .withMessage("'parameterTypes' must not be null"); } @@ -82,27 +87,33 @@ void forConstructorWhenParameterTypesIsNullThrowsException() { @Test void forConstructorWhenParameterTypesContainsNullThrowsException() { assertThatIllegalArgumentException() - .isThrownBy(() -> AutowiredInstantiationArgumentsResolver + .isThrownBy(() -> BeanInstanceSupplier .forConstructor(String.class, null)) .withMessage("'parameterTypes' must not contain null elements"); } @Test void forConstructorWhenNotFoundThrowsException() { - AutowiredInstantiationArgumentsResolver resolver = AutowiredInstantiationArgumentsResolver + BeanInstanceSupplier resolver = BeanInstanceSupplier .forConstructor(InputStream.class); Source source = new Source(SingleArgConstructor.class, resolver); RegisteredBean registerBean = source.registerBean(this.beanFactory); assertThatIllegalArgumentException() - .isThrownBy(() -> resolver.resolve(registerBean)).withMessage( + .isThrownBy(() -> resolver.get(registerBean)).withMessage( "Constructor with parameter types [java.io.InputStream] cannot be found on " + SingleArgConstructor.class.getName()); } + @Test + void forConstructorReturnsNullFactoryMethod() { + BeanInstanceSupplier resolver = BeanInstanceSupplier.forConstructor(String.class); + assertThat(resolver.getFactoryMethod()).isNull(); + } + @Test void forFactoryMethodWhenDeclaringClassIsNullThrowsException() { assertThatIllegalArgumentException() - .isThrownBy(() -> AutowiredInstantiationArgumentsResolver + .isThrownBy(() -> BeanInstanceSupplier .forFactoryMethod(null, "test")) .withMessage("'declaringClass' must not be null"); } @@ -110,7 +121,7 @@ void forFactoryMethodWhenDeclaringClassIsNullThrowsException() { @Test void forFactoryMethodWhenNameIsEmptyThrowsException() { assertThatIllegalArgumentException() - .isThrownBy(() -> AutowiredInstantiationArgumentsResolver + .isThrownBy(() -> BeanInstanceSupplier .forFactoryMethod(SingleArgFactory.class, "")) .withMessage("'methodName' must not be empty"); } @@ -119,7 +130,7 @@ void forFactoryMethodWhenNameIsEmptyThrowsException() { void forFactoryMethodWhenParameterTypesIsNullThrowsException() { assertThatIllegalArgumentException() .isThrownBy( - () -> AutowiredInstantiationArgumentsResolver.forFactoryMethod( + () -> BeanInstanceSupplier.forFactoryMethod( SingleArgFactory.class, "single", (Class[]) null)) .withMessage("'parameterTypes' must not be null"); } @@ -128,61 +139,136 @@ void forFactoryMethodWhenParameterTypesIsNullThrowsException() { void forFactoryMethodWhenParameterTypesContainsNullThrowsException() { assertThatIllegalArgumentException() .isThrownBy( - () -> AutowiredInstantiationArgumentsResolver.forFactoryMethod( + () -> BeanInstanceSupplier.forFactoryMethod( SingleArgFactory.class, "single", String.class, null)) .withMessage("'parameterTypes' must not contain null elements"); } @Test void forFactoryMethodWhenNotFoundThrowsException() { - AutowiredInstantiationArgumentsResolver resolver = AutowiredInstantiationArgumentsResolver + BeanInstanceSupplier resolver = BeanInstanceSupplier .forFactoryMethod(SingleArgFactory.class, "single", InputStream.class); Source source = new Source(String.class, resolver); RegisteredBean registerBean = source.registerBean(this.beanFactory); assertThatIllegalArgumentException() - .isThrownBy(() -> resolver.resolve(registerBean)).withMessage( + .isThrownBy(() -> resolver.get(registerBean)).withMessage( "Factory method 'single' with parameter types [java.io.InputStream] declared on class " + SingleArgFactory.class.getName() + " cannot be found"); } @Test - void resolveWithActionWhenActionIsNullThrowsException() { - AutowiredInstantiationArgumentsResolver resolver = AutowiredInstantiationArgumentsResolver + void forFactoryMethodReturnsFactoryMethod() { + BeanInstanceSupplier resolver = BeanInstanceSupplier + .forFactoryMethod(SingleArgFactory.class, "single", String.class); + Method factoryMethod = ReflectionUtils.findMethod(SingleArgFactory.class, "single", String.class); + assertThat(factoryMethod).isNotNull(); + assertThat(resolver.getFactoryMethod()).isEqualTo(factoryMethod); + } + + @Test + void withGeneratorWhenBiFunctionIsNullThrowsException() { + BeanInstanceSupplier resolver = BeanInstanceSupplier + .forConstructor(); + assertThatIllegalArgumentException() + .isThrownBy(() -> resolver.withGenerator( + (ThrowingBiFunction) null)) + .withMessage("'generator' must not be null"); + } + + @Test + void withGeneratorWhenFunctionIsNullThrowsException() { + BeanInstanceSupplier resolver = BeanInstanceSupplier + .forConstructor(); + assertThatIllegalArgumentException() + .isThrownBy(() -> resolver.withGenerator( + (ThrowingFunction) null)) + .withMessage("'generator' must not be null"); + } + + @Test + void withGeneratorWhenSupplierIsNullThrowsException() { + BeanInstanceSupplier resolver = BeanInstanceSupplier .forConstructor(); - Source source = new Source(NoArgConstructor.class, resolver); - RegisteredBean registerBean = source.registerBean(this.beanFactory); assertThatIllegalArgumentException() - .isThrownBy(() -> resolver.resolve(registerBean, null)) - .withMessage("'action' must not be null"); + .isThrownBy(() -> resolver.withGenerator( + (ThrowingSupplier) null)) + .withMessage("'generator' must not be null"); } @Test - void resolveWithActionCallsAction() { - AutowiredInstantiationArgumentsResolver resolver = AutowiredInstantiationArgumentsResolver + void getWithConstructorDoesNotSetResolvedFactoryMethod() throws Exception { + BeanInstanceSupplier resolver = BeanInstanceSupplier .forConstructor(String.class); - Source source = new Source(SingleArgConstructor.class, resolver); this.beanFactory.registerSingleton("one", "1"); + Source source = new Source(SingleArgConstructor.class, resolver); 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 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(((AutowiredArguments) result.get(0)).toArray()).containsExactly("1"); } @Test - void resolveWhenRegisteredBeanIsNullThrowsException() { - AutowiredInstantiationArgumentsResolver resolver = AutowiredInstantiationArgumentsResolver + void getWithGeneratorCallsFunction() 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(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); - assertThatIllegalArgumentException().isThrownBy(() -> resolver.resolve(null)) + assertThatIllegalArgumentException().isThrownBy(() -> resolver.get((RegisteredBean) null)) .withMessage("'registeredBean' must not be null"); } @ParameterizedResolverTest(Sources.SINGLE_ARG) - void resolveAndInstantiate(Source source) { + void getWithNoGeneratorUsesReflection(Source source) throws Exception { this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("testFactory", new SingleArgFactory()); RegisteredBean registerBean = source.registerBean(this.beanFactory); - Object instance = source.getResolver().resolveAndInstantiate(registerBean); + Object instance = source.getResolver().get(registerBean); if (instance instanceof SingleArgConstructor singleArgConstructor) { instance = singleArgConstructor.getString(); } @@ -190,12 +276,12 @@ void resolveAndInstantiate(Source source) { } @ParameterizedResolverTest(Sources.INNER_CLASS_SINGLE_ARG) - void resolveAndInstantiateNested(Source source) { + void getNestedWithNoGeneratorUsesReflection(Source source) throws Exception { this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("testFactory", new Enclosing().new InnerSingleArgFactory()); RegisteredBean registerBean = source.registerBean(this.beanFactory); - Object instance = source.getResolver().resolveAndInstantiate(registerBean); + Object instance = source.getResolver().get(registerBean); if (instance instanceof InnerSingleArgConstructor innerSingleArgConstructor) { instance = innerSingleArgConstructor.getString(); } @@ -203,38 +289,38 @@ void resolveAndInstantiateNested(Source source) { } @Test - void resolveNoArgConstructor() { + void resolveArgumentsWithNoArgConstructor() { RootBeanDefinition beanDefinition = new RootBeanDefinition( NoArgConstructor.class); this.beanFactory.registerBeanDefinition("test", beanDefinition); RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "test"); - AutowiredArguments resolved = AutowiredInstantiationArgumentsResolver - .forConstructor().resolve(registeredBean); + AutowiredArguments resolved = BeanInstanceSupplier + .forConstructor().resolveArguments(registeredBean); assertThat(resolved.toArray()).isEmpty(); } @ParameterizedResolverTest(Sources.SINGLE_ARG) - void resolveSingleArgConstructor(Source source) { + void resolveArgumentsWithSingleArgConstructor(Source source) { this.beanFactory.registerSingleton("one", "1"); RegisteredBean registeredBean = source.registerBean(this.beanFactory); - assertThat(source.getResolver().resolve(registeredBean).toArray()) + assertThat(source.getResolver().resolveArguments(registeredBean).toArray()) .containsExactly("1"); } @ParameterizedResolverTest(Sources.INNER_CLASS_SINGLE_ARG) - void resolvedNestedSingleArgConstructor(Source source) { + void resolveArgumentsWithNestedSingleArgConstructor(Source source) { this.beanFactory.registerSingleton("one", "1"); RegisteredBean registeredBean = source.registerBean(this.beanFactory); - assertThat(source.getResolver().resolve(registeredBean).toArray()) + assertThat(source.getResolver().resolveArguments(registeredBean).toArray()) .containsExactly("1"); } @ParameterizedResolverTest(Sources.SINGLE_ARG) - void resolveRequiredDependencyNotPresentThrowsUnsatisfiedDependencyException( + void resolveArgumentsWithRequiredDependencyNotPresentThrowsUnsatisfiedDependencyException( Source source) { RegisteredBean registeredBean = source.registerBean(this.beanFactory); assertThatExceptionOfType(UnsatisfiedDependencyException.class) - .isThrownBy(() -> source.getResolver().resolve(registeredBean)) + .isThrownBy(() -> source.getResolver().resolveArguments(registeredBean)) .satisfies(ex -> { assertThat(ex.getBeanName()).isEqualTo("testBean"); assertThat(ex.getInjectionPoint()).isNotNull(); @@ -244,16 +330,16 @@ void resolveRequiredDependencyNotPresentThrowsUnsatisfiedDependencyException( } @Test - void resolveInInstanceSupplierWithSelfReferenceThrowsException() { + void resolveArgumentsInInstanceSupplierWithSelfReferenceThrowsException() { // 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()); RootBeanDefinition beanDefinition = new RootBeanDefinition(String.class); beanDefinition.setInstanceSupplier(InstanceSupplier.of(registeredBean -> { - AutowiredArguments args = AutowiredInstantiationArgumentsResolver + AutowiredArguments args = BeanInstanceSupplier .forFactoryMethod(SingleArgFactory.class, "single", String.class) - .resolve(registeredBean); - return new SingleArgFactory().single((String) args.get(0)); + .resolveArguments(registeredBean); + return new SingleArgFactory().single(args.get(0)); })); this.beanFactory.registerBeanDefinition("test", beanDefinition); assertThatExceptionOfType(UnsatisfiedDependencyException.class) @@ -261,83 +347,83 @@ void resolveInInstanceSupplierWithSelfReferenceThrowsException() { } @ParameterizedResolverTest(Sources.ARRAY_OF_BEANS) - void resolveArrayOfBeans(Source source) { + void resolveArgumentsWithArrayOfBeans(Source source) { this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("two", "2"); RegisteredBean registerBean = source.registerBean(this.beanFactory); - AutowiredArguments arguments = source.getResolver().resolve(registerBean); + AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean); assertThat(arguments.toArray()).hasSize(1); assertThat((Object[]) arguments.get(0)).containsExactly("1", "2"); } @ParameterizedResolverTest(Sources.ARRAY_OF_BEANS) - void resolveRequiredArrayOfBeansInjectEmptyArray(Source source) { + void resolveArgumentsWithRequiredArrayOfBeansInjectEmptyArray(Source source) { RegisteredBean registerBean = source.registerBean(this.beanFactory); - AutowiredArguments arguments = source.getResolver().resolve(registerBean); + AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean); assertThat(arguments.toArray()).hasSize(1); assertThat((Object[]) arguments.get(0)).isEmpty(); } @ParameterizedResolverTest(Sources.LIST_OF_BEANS) - void resolveListOfBeans(Source source) { + void resolveArgumentsWithListOfBeans(Source source) { this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("two", "2"); 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.getObject(0)).isInstanceOf(List.class).asList() .containsExactly("1", "2"); } @ParameterizedResolverTest(Sources.LIST_OF_BEANS) - void resolveRequiredListOfBeansInjectEmptyList(Source source) { + void resolveArgumentsWithRequiredListOfBeansInjectEmptyList(Source source) { RegisteredBean registerBean = source.registerBean(this.beanFactory); - AutowiredArguments arguments = source.getResolver().resolve(registerBean); + AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean); assertThat(arguments.toArray()).hasSize(1); assertThat((List) arguments.get(0)).isEmpty(); } @ParameterizedResolverTest(Sources.SET_OF_BEANS) @SuppressWarnings("unchecked") - void resolveSetOfBeans(Source source) { + void resolveArgumentsWithSetOfBeans(Source source) { this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("two", "2"); RegisteredBean registerBean = source.registerBean(this.beanFactory); - AutowiredArguments arguments = source.getResolver().resolve(registerBean); + AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean); assertThat(arguments.toArray()).hasSize(1); assertThat((Set) arguments.get(0)).containsExactly("1", "2"); } @ParameterizedResolverTest(Sources.SET_OF_BEANS) - void resolveRequiredSetOfBeansInjectEmptySet(Source source) { + void resolveArgumentsWithRequiredSetOfBeansInjectEmptySet(Source source) { RegisteredBean registerBean = source.registerBean(this.beanFactory); - AutowiredArguments arguments = source.getResolver().resolve(registerBean); + AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean); assertThat(arguments.toArray()).hasSize(1); assertThat((Set) arguments.get(0)).isEmpty(); } @ParameterizedResolverTest(Sources.MAP_OF_BEANS) @SuppressWarnings("unchecked") - void resolveMapOfBeans(Source source) { + void resolveArgumentsWithMapOfBeans(Source source) { this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("two", "2"); RegisteredBean registerBean = source.registerBean(this.beanFactory); - AutowiredArguments arguments = source.getResolver().resolve(registerBean); + AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean); assertThat(arguments.toArray()).hasSize(1); assertThat((Map) arguments.get(0)) .containsExactly(entry("one", "1"), entry("two", "2")); } @ParameterizedResolverTest(Sources.MAP_OF_BEANS) - void resolveRequiredMapOfBeansInjectEmptySet(Source source) { + void resolveArgumentsWithRequiredMapOfBeansInjectEmptySet(Source source) { RegisteredBean registerBean = source.registerBean(this.beanFactory); - AutowiredArguments arguments = source.getResolver().resolve(registerBean); + AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean); assertThat(arguments.toArray()).hasSize(1); assertThat((Map) arguments.get(0)).isEmpty(); } @ParameterizedResolverTest(Sources.MULTI_ARGS) - void resolveMultiArgsConstructor(Source source) { + void resolveArgumentsWithMultiArgsConstructor(Source source) { ResourceLoader resourceLoader = new DefaultResourceLoader(); Environment environment = mock(Environment.class); this.beanFactory.registerResolvableDependency(ResourceLoader.class, @@ -345,7 +431,7 @@ void resolveMultiArgsConstructor(Source source) { this.beanFactory.registerSingleton("environment", environment); this.beanFactory.registerSingleton("one", "1"); 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.getObject(0)).isEqualTo(resourceLoader); assertThat(arguments.getObject(1)).isEqualTo(environment); @@ -354,7 +440,7 @@ void resolveMultiArgsConstructor(Source source) { } @ParameterizedResolverTest(Sources.MIXED_ARGS) - void resolveMixedArgsConstructorWithUserValue(Source source) { + void resolveArgumentsWithMixedArgsConstructorWithUserValue(Source source) { ResourceLoader resourceLoader = new DefaultResourceLoader(); Environment environment = mock(Environment.class); this.beanFactory.registerResolvableDependency(ResourceLoader.class, @@ -367,7 +453,7 @@ void resolveMixedArgsConstructorWithUserValue(Source source) { beanDefinition.getConstructorArgumentValues() .addIndexedArgumentValue(1, "user-value"); }); - AutowiredArguments arguments = source.getResolver().resolve(registerBean); + AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean); assertThat(arguments.toArray()).hasSize(3); assertThat(arguments.getObject(0)).isEqualTo(resourceLoader); assertThat(arguments.getObject(1)).isEqualTo("user-value"); @@ -375,7 +461,7 @@ void resolveMixedArgsConstructorWithUserValue(Source source) { } @ParameterizedResolverTest(Sources.MIXED_ARGS) - void resolveMixedArgsConstructorWithUserBeanReference(Source source) { + void resolveArgumentsWithMixedArgsConstructorWithUserBeanReference(Source source) { ResourceLoader resourceLoader = new DefaultResourceLoader(); Environment environment = mock(Environment.class); this.beanFactory.registerResolvableDependency(ResourceLoader.class, @@ -390,7 +476,7 @@ void resolveMixedArgsConstructorWithUserBeanReference(Source source) { beanDefinition.getConstructorArgumentValues() .addIndexedArgumentValue(1, new RuntimeBeanReference("two")); }); - AutowiredArguments arguments = source.getResolver().resolve(registerBean); + AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean); assertThat(arguments.toArray()).hasSize(3); assertThat(arguments.getObject(0)).isEqualTo(resourceLoader); assertThat(arguments.getObject(1)).isEqualTo("2"); @@ -398,9 +484,9 @@ void resolveMixedArgsConstructorWithUserBeanReference(Source source) { } @Test - void resolveUserValueWithTypeConversionRequired() { + void resolveArgumentsWithUserValueWithTypeConversionRequired() { Source source = new Source(CharDependency.class, - AutowiredInstantiationArgumentsResolver.forConstructor(char.class)); + BeanInstanceSupplier.forConstructor(char.class)); RegisteredBean registerBean = source.registerBean(this.beanFactory, beanDefinition -> { beanDefinition @@ -408,37 +494,37 @@ void resolveUserValueWithTypeConversionRequired() { beanDefinition.getConstructorArgumentValues() .addIndexedArgumentValue(0, "\\"); }); - AutowiredArguments arguments = source.getResolver().resolve(registerBean); + AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean); assertThat(arguments.toArray()).hasSize(1); assertThat(arguments.getObject(0)).isInstanceOf(Character.class).isEqualTo('\\'); } @ParameterizedResolverTest(Sources.SINGLE_ARG) - void resolveUserValueWithBeanReference(Source source) { + void resolveArgumentsWithUserValueWithBeanReference(Source source) { this.beanFactory.registerSingleton("stringBean", "string"); RegisteredBean registerBean = source.registerBean(this.beanFactory, beanDefinition -> beanDefinition.getConstructorArgumentValues() .addIndexedArgumentValue(0, new RuntimeBeanReference("stringBean"))); - AutowiredArguments arguments = source.getResolver().resolve(registerBean); + AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean); assertThat(arguments.toArray()).hasSize(1); assertThat(arguments.getObject(0)).isEqualTo("string"); } @ParameterizedResolverTest(Sources.SINGLE_ARG) - void resolveUserValueWithBeanDefinition(Source source) { + void resolveArgumentsWithUserValueWithBeanDefinition(Source source) { AbstractBeanDefinition userValue = BeanDefinitionBuilder .rootBeanDefinition(String.class, () -> "string").getBeanDefinition(); RegisteredBean registerBean = source.registerBean(this.beanFactory, beanDefinition -> beanDefinition.getConstructorArgumentValues() .addIndexedArgumentValue(0, userValue)); - AutowiredArguments arguments = source.getResolver().resolve(registerBean); + AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean); assertThat(arguments.toArray()).hasSize(1); assertThat(arguments.getObject(0)).isEqualTo("string"); } @ParameterizedResolverTest(Sources.SINGLE_ARG) - void resolveUserValueThatIsAlreadyResolved(Source source) { + void resolveArgumentsWithUserValueThatIsAlreadyResolved(Source source) { RegisteredBean registerBean = source.registerBean(this.beanFactory); BeanDefinition mergedBeanDefinition = this.beanFactory .getMergedBeanDefinition("testBean"); @@ -446,13 +532,13 @@ void resolveUserValueThatIsAlreadyResolved(Source source) { valueHolder.setConvertedValue("this is an a"); mergedBeanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, valueHolder); - AutowiredArguments arguments = source.getResolver().resolve(registerBean); + AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean); assertThat(arguments.toArray()).hasSize(1); assertThat(arguments.getObject(0)).isEqualTo("this is an a"); } @Test - void resolveWhenUsingShortcutsInjectsDirectly() { + void resolveArgumentsWhenUsingShortcutsInjectsDirectly() { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory() { @Override @@ -462,35 +548,35 @@ protected Map findAutowireCandidates(String beanName, } }; - AutowiredInstantiationArgumentsResolver resolver = AutowiredInstantiationArgumentsResolver + BeanInstanceSupplier resolver = BeanInstanceSupplier .forConstructor(String.class); Source source = new Source(String.class, resolver); beanFactory.registerSingleton("one", "1"); RegisteredBean registeredBean = source.registerBean(beanFactory); assertThatExceptionOfType(AssertionError.class) - .isThrownBy(() -> resolver.resolve(registeredBean)); - assertThat(resolver.withShortcuts("one").resolve(registeredBean).toArray()) + .isThrownBy(() -> resolver.resolveArguments(registeredBean)); + assertThat(resolver.withShortcuts("one").resolveArguments(registeredBean).toArray()) .containsExactly("1"); } @Test - void resolveRegistersDependantBeans() { - AutowiredInstantiationArgumentsResolver resolver = AutowiredInstantiationArgumentsResolver + void resolveArgumentsRegistersDependantBeans() { + BeanInstanceSupplier resolver = BeanInstanceSupplier .forConstructor(String.class); Source source = new Source(SingleArgConstructor.class, resolver); - beanFactory.registerSingleton("one", "1"); + this.beanFactory.registerSingleton("one", "1"); RegisteredBean registeredBean = source.registerBean(this.beanFactory); - resolver.resolve(registeredBean); + resolver.resolveArguments(registeredBean); 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) @ParameterizedTest @ArgumentsSource(SourcesArguments.class) - static @interface ParameterizedResolverTest { + @interface ParameterizedResolverTest { Sources value(); @@ -510,8 +596,7 @@ public void accept(ParameterizedResolverTest annotation) { } @Override - public Stream provideArguments(ExtensionContext context) - throws Exception { + public Stream provideArguments(ExtensionContext context) { return this.source.provideArguments(context); } @@ -523,27 +608,25 @@ public Stream provideArguments(ExtensionContext context) enum Sources { SINGLE_ARG { - @Override protected void setup() { - add(SingleArgConstructor.class, AutowiredInstantiationArgumentsResolver + add(SingleArgConstructor.class, BeanInstanceSupplier .forConstructor(String.class)); add(String.class, - AutowiredInstantiationArgumentsResolver.forFactoryMethod( + BeanInstanceSupplier.forFactoryMethod( SingleArgFactory.class, "single", String.class)); } }, INNER_CLASS_SINGLE_ARG { - @Override protected void setup() { add(Enclosing.InnerSingleArgConstructor.class, - AutowiredInstantiationArgumentsResolver + BeanInstanceSupplier .forConstructor(String.class)); add(String.class, - AutowiredInstantiationArgumentsResolver.forFactoryMethod( + BeanInstanceSupplier.forFactoryMethod( Enclosing.InnerSingleArgFactory.class, "single", String.class)); } @@ -551,71 +634,66 @@ protected void setup() { }, ARRAY_OF_BEANS { - @Override protected void setup() { add(BeansCollectionConstructor.class, - AutowiredInstantiationArgumentsResolver + BeanInstanceSupplier .forConstructor(String[].class)); add(String.class, - AutowiredInstantiationArgumentsResolver.forFactoryMethod( + BeanInstanceSupplier.forFactoryMethod( BeansCollectionFactory.class, "array", String[].class)); } }, LIST_OF_BEANS { - @Override protected void setup() { add(BeansCollectionConstructor.class, - AutowiredInstantiationArgumentsResolver + BeanInstanceSupplier .forConstructor(List.class)); add(String.class, - AutowiredInstantiationArgumentsResolver.forFactoryMethod( + BeanInstanceSupplier.forFactoryMethod( BeansCollectionFactory.class, "list", List.class)); } }, SET_OF_BEANS { - @Override protected void setup() { add(BeansCollectionConstructor.class, - AutowiredInstantiationArgumentsResolver + BeanInstanceSupplier .forConstructor(Set.class)); add(String.class, - AutowiredInstantiationArgumentsResolver.forFactoryMethod( + BeanInstanceSupplier.forFactoryMethod( BeansCollectionFactory.class, "set", Set.class)); } }, MAP_OF_BEANS { - @Override protected void setup() { add(BeansCollectionConstructor.class, - AutowiredInstantiationArgumentsResolver + BeanInstanceSupplier .forConstructor(Map.class)); add(String.class, - AutowiredInstantiationArgumentsResolver.forFactoryMethod( + BeanInstanceSupplier.forFactoryMethod( BeansCollectionFactory.class, "map", Map.class)); } }, MULTI_ARGS { - @Override protected void setup() { add(MultiArgsConstructor.class, - AutowiredInstantiationArgumentsResolver.forConstructor( + BeanInstanceSupplier.forConstructor( ResourceLoader.class, Environment.class, ObjectProvider.class)); add(String.class, - AutowiredInstantiationArgumentsResolver.forFactoryMethod( + BeanInstanceSupplier.forFactoryMethod( MultiArgsFactory.class, "multiArgs", ResourceLoader.class, Environment.class, ObjectProvider.class)); } @@ -623,14 +701,13 @@ protected void setup() { }, MIXED_ARGS { - @Override protected void setup() { add(MixedArgsConstructor.class, - AutowiredInstantiationArgumentsResolver.forConstructor( + BeanInstanceSupplier.forConstructor( ResourceLoader.class, String.class, Environment.class)); add(String.class, - AutowiredInstantiationArgumentsResolver.forFactoryMethod( + BeanInstanceSupplier.forFactoryMethod( MixedArgsFactory.class, "mixedArgs", ResourceLoader.class, String.class, Environment.class)); } @@ -639,7 +716,7 @@ protected void setup() { private final List arguments; - private Sources() { + Sources() { this.arguments = new ArrayList<>(); setup(); } @@ -647,7 +724,7 @@ private Sources() { protected abstract void setup(); protected final void add(Class beanClass, - AutowiredInstantiationArgumentsResolver resolver) { + BeanInstanceSupplier resolver) { this.arguments.add(Arguments.of(new Source(beanClass, resolver))); } @@ -657,16 +734,12 @@ final Stream provideArguments(ExtensionContext context) { } - static class Source { - - private final Class beanClass; + static class BeanRegistrar { - private final AutowiredInstantiationArgumentsResolver resolver; + final Class beanClass; - public Source(Class beanClass, - AutowiredInstantiationArgumentsResolver resolver) { + public BeanRegistrar(Class beanClass) { this.beanClass = beanClass; - this.resolver = resolver; } RegisteredBean registerBean(DefaultListableBeanFactory beanFactory) { @@ -685,8 +758,19 @@ RegisteredBean registerBean(DefaultListableBeanFactory beanFactory, beanFactory.registerBeanDefinition(beanName, beanDefinition); return RegisteredBean.of(beanFactory, beanName); } + } + + static class Source extends BeanRegistrar { + + private final BeanInstanceSupplier resolver; + + public Source(Class beanClass, + BeanInstanceSupplier resolver) { + super(beanClass); + this.resolver = resolver; + } - AutowiredInstantiationArgumentsResolver getResolver() { + BeanInstanceSupplier getResolver() { return this.resolver; } @@ -715,7 +799,7 @@ static class SingleArgConstructor { } String getString() { - return string; + return this.string; } } @@ -834,7 +918,7 @@ static class CharDependency { } - static interface MethodOnInterface { + interface MethodOnInterface { default String test() { return "Test"; diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java index de04e7d9ab2d..c1a486f2f6ac 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java @@ -172,7 +172,7 @@ void generateWhenHasPrivateConstructor() { instanceSupplier); assertThat(bean).isInstanceOf(TestBeanWithPrivateConstructor.class); assertThat(compiled.getSourceFile()) - .contains("resolveAndInstantiate(registeredBean)"); + .contains("return BeanInstanceSupplier.forConstructor();"); }); assertThat(getReflectionHints().getTypeHint(TestBeanWithPrivateConstructor.class)) .satisfies(hasConstructorWithMode(ExecutableMode.INVOKE)); @@ -211,7 +211,8 @@ void generateWhenHasPrivateStaticFactoryMethodWithNoArg() { assertThat(bean).isInstanceOf(String.class); assertThat(bean).isEqualTo("Hello"); assertThat(compiled.getSourceFile()) - .contains("resolveAndInstantiate(registeredBean)"); + .contains("BeanInstanceSupplier.forFactoryMethod") + .doesNotContain("withGenerator"); }); assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class)) .satisfies(hasMethodWithMode(ExecutableMode.INVOKE)); @@ -271,7 +272,7 @@ void generateWhenHasStaticFactoryMethodCheckedException() { Integer bean = getBean(beanFactory, beanDefinition, instanceSupplier); assertThat(bean).isInstanceOf(Integer.class); assertThat(bean).isEqualTo(42); - assertThat(compiled.getSourceFile()).contains(") throws Exception {"); + assertThat(compiled.getSourceFile()).doesNotContain(") throws Exception {"); }); assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class)) .satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT)); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/RootBeanDefinitionTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/RootBeanDefinitionTests.java new file mode 100644 index 000000000000..7e1a5481b5a2 --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/RootBeanDefinitionTests.java @@ -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(); + } + +}