Skip to content

Commit 75ab47b

Browse files
snicollphilwebb
authored andcommitted
Link factoryMethod consistently in AOT-generated bean definitions
Closes gh-28748
1 parent f2d31b7 commit 75ab47b

File tree

9 files changed

+501
-315
lines changed

9 files changed

+501
-315
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredArguments.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* @author Phillip Webb
2727
* @author Stephane Nicoll
2828
* @since 6.0
29-
* @see AutowiredInstantiationArgumentsResolver
29+
* @see BeanInstanceSupplier
3030
* @see AutowiredMethodArgumentsResolver
3131
*/
3232
@FunctionalInterface

spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredMethodArgumentsResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public static AutowiredMethodArgumentsResolver forRequiredMethod(String methodNa
105105
}
106106

107107
/**
108-
* Return a new {@link AutowiredInstantiationArgumentsResolver} instance
108+
* Return a new {@link AutowiredMethodArgumentsResolver} instance
109109
* that uses direct bean name injection shortcuts for specific parameters.
110110
* @param beanNames the bean names to use as shortcuts (aligned with the
111111
* method parameters)
Lines changed: 112 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.springframework.beans.factory.config.DependencyDescriptor;
4141
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
4242
import org.springframework.beans.factory.support.BeanDefinitionValueResolver;
43+
import org.springframework.beans.factory.support.InstanceSupplier;
4344
import org.springframework.beans.factory.support.RegisteredBean;
4445
import org.springframework.beans.factory.support.RootBeanDefinition;
4546
import org.springframework.core.CollectionFactory;
@@ -49,77 +50,91 @@
4950
import org.springframework.util.ClassUtils;
5051
import org.springframework.util.ObjectUtils;
5152
import org.springframework.util.ReflectionUtils;
53+
import org.springframework.util.function.ThrowingBiFunction;
5254
import org.springframework.util.function.ThrowingFunction;
55+
import org.springframework.util.function.ThrowingSupplier;
5356

5457
/**
55-
* Resolver used to support the autowiring of constructors or factory methods.
56-
* Typically used in AOT-processed applications as a targeted alternative to the
57-
* reflection based injection.
58+
* Specialized {@link InstanceSupplier} that provides the factory {@link Method}
59+
* used to instantiate the underlying bean instance, if any. Transparently
60+
* handles resolution of {@link AutowiredArguments} if necessary. Typically used
61+
* in AOT-processed applications as a targeted alternative to the reflection
62+
* based injection.
5863
* <p>
59-
* When resolving arguments in a native image, the {@link Constructor} or
60-
* {@link Method} being used must be marked with an
61-
* {@link ExecutableMode#INTROSPECT introspection} hint so that parameter
62-
* annotations can be read. Full {@link ExecutableMode#INVOKE invocation} hints
63-
* are only required if the {@code resolveAndInstantiate} methods of this class
64-
* are being used (typically to support private constructors, methods or
65-
* classes).
64+
* If no {@code generator} is provided, reflection is used to instantiate the
65+
* bean instance, and full {@link ExecutableMode#INVOKE invocation} hints are
66+
* contributed. Multiple generator callback styles are supported:
67+
* <ul>
68+
* <li>A function with the {@code registeredBean} and resolved {@code arguments}
69+
* for executables that require arguments resolution. An
70+
* {@link ExecutableMode#INTROSPECT introspection} hint is added so that
71+
* parameter annotations can be read </li>
72+
* <li>A function with only the {@code registeredBean} for simpler cases that
73+
* do not require resolution of arguments</li>
74+
* <li>A supplier when a method reference can be used</li>
75+
* </ul>
76+
* Generator callbacks handle checked exceptions so that the caller does not
77+
* have to deal with it.
6678
*
6779
* @author Phillip Webb
6880
* @author Stephane Nicoll
6981
* @since 6.0
7082
* @see AutowiredArguments
7183
*/
72-
public final class AutowiredInstantiationArgumentsResolver extends AutowiredElementResolver {
84+
public final class BeanInstanceSupplier extends AutowiredElementResolver implements InstanceSupplier<Object> {
7385

7486
private final ExecutableLookup lookup;
7587

88+
@Nullable
89+
private final ThrowingBiFunction<RegisteredBean, AutowiredArguments, Object> generator;
90+
7691
@Nullable
7792
private final String[] shortcuts;
7893

7994

80-
private AutowiredInstantiationArgumentsResolver(ExecutableLookup lookup,
95+
private BeanInstanceSupplier(ExecutableLookup lookup,
96+
@Nullable ThrowingBiFunction<RegisteredBean, AutowiredArguments, Object> generator,
8197
@Nullable String[] shortcuts) {
82-
8398
this.lookup = lookup;
99+
this.generator = generator;
84100
this.shortcuts = shortcuts;
85101
}
86102

87-
88103
/**
89-
* Create a {@link AutowiredInstantiationArgumentsResolver} that resolves
104+
* Create a {@link BeanInstanceSupplier} that resolves
90105
* arguments for the specified bean constructor.
91106
* @param parameterTypes the constructor parameter types
92-
* @return a new {@link AutowiredInstantiationArgumentsResolver} instance
107+
* @return a new {@link BeanInstanceSupplier} instance
93108
*/
94-
public static AutowiredInstantiationArgumentsResolver forConstructor(
109+
public static BeanInstanceSupplier forConstructor(
95110
Class<?>... parameterTypes) {
96111

97112
Assert.notNull(parameterTypes, "'parameterTypes' must not be null");
98113
Assert.noNullElements(parameterTypes,
99114
"'parameterTypes' must not contain null elements");
100-
return new AutowiredInstantiationArgumentsResolver(
101-
new ConstructorLookup(parameterTypes), null);
115+
return new BeanInstanceSupplier(
116+
new ConstructorLookup(parameterTypes), null, null);
102117
}
103118

104119
/**
105-
* Create a new {@link AutowiredInstantiationArgumentsResolver} that
120+
* Create a new {@link BeanInstanceSupplier} that
106121
* resolves arguments for the specified factory method.
107122
* @param declaringClass the class that declares the factory method
108123
* @param methodName the factory method name
109124
* @param parameterTypes the factory method parameter types
110-
* @return a new {@link AutowiredInstantiationArgumentsResolver} instance
125+
* @return a new {@link BeanInstanceSupplier} instance
111126
*/
112-
public static AutowiredInstantiationArgumentsResolver forFactoryMethod(
127+
public static BeanInstanceSupplier forFactoryMethod(
113128
Class<?> declaringClass, String methodName, Class<?>... parameterTypes) {
114129

115130
Assert.notNull(declaringClass, "'declaringClass' must not be null");
116131
Assert.hasText(methodName, "'methodName' must not be empty");
117132
Assert.notNull(parameterTypes, "'parameterTypes' must not be null");
118133
Assert.noNullElements(parameterTypes,
119134
"'parameterTypes' must not contain null elements");
120-
return new AutowiredInstantiationArgumentsResolver(
135+
return new BeanInstanceSupplier(
121136
new FactoryMethodLookup(declaringClass, methodName, parameterTypes),
122-
null);
137+
null, null);
123138
}
124139

125140

@@ -128,74 +143,92 @@ ExecutableLookup getLookup() {
128143
}
129144

130145
/**
131-
* Return a new {@link AutowiredInstantiationArgumentsResolver} instance
132-
* that uses direct bean name injection shortcuts for specific parameters.
133-
* @param beanNames the bean names to use as shortcuts (aligned with the
134-
* constructor or factory method parameters)
135-
* @return a new {@link AutowiredInstantiationArgumentsResolver} instance
136-
* that uses the shortcuts
146+
* Return a new {@link BeanInstanceSupplier} instance that uses the specified
147+
* {@code generator} bi-function to instantiate the underlying bean.
148+
* @param generator a {@link ThrowingBiFunction} that uses the
149+
* {@link RegisteredBean} and resolved {@link AutowiredArguments} to
150+
* instantiate the underlying bean
151+
* @return a new {@link BeanInstanceSupplier} instance with the specified
152+
* generator
137153
*/
138-
public AutowiredInstantiationArgumentsResolver withShortcuts(String... beanNames) {
139-
return new AutowiredInstantiationArgumentsResolver(this.lookup, beanNames);
154+
public BeanInstanceSupplier withGenerator(
155+
ThrowingBiFunction<RegisteredBean, AutowiredArguments, Object> generator) {
156+
Assert.notNull(generator, "'generator' must not be null");
157+
return new BeanInstanceSupplier(this.lookup, generator, this.shortcuts);
140158
}
141159

142160
/**
143-
* Resolve arguments for the specified registered bean and provide them to
144-
* the given generator in order to return a result.
145-
* @param registeredBean the registered bean
146-
* @param generator the generator to execute with the resolved constructor
147-
* or factory method arguments
161+
* Return a new {@link BeanInstanceSupplier} instance that uses the specified
162+
* {@code generator} function to instantiate the underlying bean.
163+
* @param generator a {@link ThrowingFunction} that uses the
164+
* {@link RegisteredBean} to instantiate the underlying bean
165+
* @return a new {@link BeanInstanceSupplier} instance with the specified
166+
* generator
148167
*/
149-
public <T> T resolve(RegisteredBean registeredBean,
150-
ThrowingFunction<AutowiredArguments, T> generator) {
151-
152-
Assert.notNull(registeredBean, "'registeredBean' must not be null");
153-
Assert.notNull(generator, "'action' must not be null");
154-
AutowiredArguments resolved = resolveArguments(registeredBean,
155-
this.lookup.get(registeredBean));
156-
return generator.apply(resolved);
168+
public BeanInstanceSupplier withGenerator(
169+
ThrowingFunction<RegisteredBean, Object> generator) {
170+
Assert.notNull(generator, "'generator' must not be null");
171+
return new BeanInstanceSupplier(this.lookup, (registeredBean, args) ->
172+
generator.apply(registeredBean), this.shortcuts);
157173
}
158174

159175
/**
160-
* Resolve arguments for the specified registered bean.
161-
* @param registeredBean the registered bean
162-
* @return the resolved constructor or factory method arguments
176+
* Return a new {@link BeanInstanceSupplier} instance that uses the specified
177+
* {@code generator} supplier to instantiate the underlying bean.
178+
* @param generator a {@link ThrowingSupplier} to instantiate the underlying
179+
* bean
180+
* @return a new {@link BeanInstanceSupplier} instance with the specified
181+
* generator
163182
*/
164-
public AutowiredArguments resolve(RegisteredBean registeredBean) {
165-
Assert.notNull(registeredBean, "'registeredBean' must not be null");
166-
return resolveArguments(registeredBean, this.lookup.get(registeredBean));
183+
public BeanInstanceSupplier withGenerator(ThrowingSupplier<Object> generator) {
184+
Assert.notNull(generator, "'generator' must not be null");
185+
return new BeanInstanceSupplier(this.lookup, (registeredBean, args) ->
186+
generator.get(), this.shortcuts);
167187
}
168188

169189
/**
170-
* Resolve arguments for the specified registered bean and instantiate a new
171-
* instance using reflection.
172-
* @param registeredBean the registered bean
173-
* @return an instance of the bean
190+
* Return a new {@link BeanInstanceSupplier} instance
191+
* that uses direct bean name injection shortcuts for specific parameters.
192+
* @param beanNames the bean names to use as shortcuts (aligned with the
193+
* constructor or factory method parameters)
194+
* @return a new {@link BeanInstanceSupplier} instance
195+
* that uses the shortcuts
174196
*/
175-
@SuppressWarnings("unchecked")
176-
public <T> T resolveAndInstantiate(RegisteredBean registeredBean) {
177-
return (T) resolveAndInstantiate(registeredBean, Object.class);
197+
public BeanInstanceSupplier withShortcuts(String... beanNames) {
198+
return new BeanInstanceSupplier(this.lookup, this.generator, beanNames);
199+
}
200+
201+
@Override
202+
public Object get(RegisteredBean registeredBean) throws Exception {
203+
Assert.notNull(registeredBean, "'registeredBean' must not be null");
204+
Executable executable = this.lookup.get(registeredBean);
205+
AutowiredArguments arguments = resolveArguments(registeredBean, executable);
206+
if (this.generator != null) {
207+
return this.generator.apply(registeredBean, arguments);
208+
}
209+
else {
210+
return instantiate(registeredBean.getBeanFactory(), executable,
211+
arguments.toArray());
212+
}
213+
}
214+
215+
@Nullable
216+
@Override
217+
public Method getFactoryMethod() {
218+
if (this.lookup instanceof FactoryMethodLookup factoryMethodLookup) {
219+
return factoryMethodLookup.get();
220+
}
221+
return null;
178222
}
179223

180224
/**
181-
* Resolve arguments for the specified registered bean and instantiate a new
182-
* instance using reflection.
225+
* Resolve arguments for the specified registered bean.
183226
* @param registeredBean the registered bean
184-
* @param requiredType the required result type
185-
* @return an instance of the bean
227+
* @return the resolved constructor or factory method arguments
186228
*/
187-
@SuppressWarnings("unchecked")
188-
public <T> T resolveAndInstantiate(RegisteredBean registeredBean,
189-
Class<T> requiredType) {
190-
229+
AutowiredArguments resolveArguments(RegisteredBean registeredBean) {
191230
Assert.notNull(registeredBean, "'registeredBean' must not be null");
192-
Assert.notNull(registeredBean, "'requiredType' must not be null");
193-
Executable executable = this.lookup.get(registeredBean);
194-
AutowiredArguments arguments = resolveArguments(registeredBean, executable);
195-
Object instance = instantiate(registeredBean.getBeanFactory(), executable,
196-
arguments.toArray());
197-
Assert.isInstanceOf(requiredType, instance);
198-
return (T) instance;
231+
return resolveArguments(registeredBean, this.lookup.get(registeredBean));
199232
}
200233

201234
private AutowiredArguments resolveArguments(RegisteredBean registeredBean,
@@ -233,9 +266,6 @@ private AutowiredArguments resolveArguments(RegisteredBean registeredBean,
233266
autowiredBeans, parameter, dependencyDescriptor, argumentValue);
234267
}
235268
registerDependentBeans(beanFactory, beanName, autowiredBeans);
236-
if (executable instanceof Method method) {
237-
mergedBeanDefinition.setResolvedFactoryMethod(method);
238-
}
239269
return AutowiredArguments.of(resolved);
240270
}
241271

@@ -403,10 +433,12 @@ private static class ConstructorLookup extends ExecutableLookup {
403433

404434
private final Class<?>[] parameterTypes;
405435

436+
406437
ConstructorLookup(Class<?>[] parameterTypes) {
407438
this.parameterTypes = parameterTypes;
408439
}
409440

441+
410442
@Override
411443
public Executable get(RegisteredBean registeredBean) {
412444
Class<?> beanClass = registeredBean.getBeanClass();
@@ -453,6 +485,10 @@ private static class FactoryMethodLookup extends ExecutableLookup {
453485

454486
@Override
455487
public Executable get(RegisteredBean registeredBean) {
488+
return get();
489+
}
490+
491+
Method get() {
456492
Method method = ReflectionUtils.findMethod(this.declaringClass,
457493
this.methodName, this.parameterTypes);
458494
Assert.notNull(method, () -> String.format("%s cannot be found", this));

0 commit comments

Comments
 (0)