Skip to content

Commit 85388aa

Browse files
committed
Add AOT support for generic constructor argument values
This commit improves compatibility with the core container when running in AOT mode by adding support for generic constructor argument values. Previously, these were ignored altogether. We now have code generation support for them as well as resolution that is similar to what AbstractAutowiredCapableBeanFactory does in a regular runtime. This commit also improves AOT support for XML bean configurations by adding more support for TypedStringValue and inner bean definitions. Closes gh-31420
1 parent ca4d0d7 commit 85388aa

File tree

11 files changed

+445
-57
lines changed

11 files changed

+445
-57
lines changed

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

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Collection;
2525
import java.util.Collections;
2626
import java.util.HashMap;
27+
import java.util.List;
2728
import java.util.Map;
2829
import java.util.Objects;
2930
import java.util.Set;
@@ -43,6 +44,7 @@
4344
import org.springframework.beans.factory.FactoryBean;
4445
import org.springframework.beans.factory.config.BeanDefinition;
4546
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
47+
import org.springframework.beans.factory.config.ConstructorArgumentValues;
4648
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
4749
import org.springframework.beans.factory.support.AbstractBeanDefinition;
4850
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
@@ -168,16 +170,38 @@ private void addInitDestroyHint(Class<?> beanUserClass, String methodName) {
168170
}
169171

170172
private void addConstructorArgumentValues(CodeBlock.Builder code, BeanDefinition beanDefinition) {
171-
Map<Integer, ValueHolder> argumentValues =
172-
beanDefinition.getConstructorArgumentValues().getIndexedArgumentValues();
173-
if (!argumentValues.isEmpty()) {
174-
argumentValues.forEach((index, valueHolder) -> {
173+
ConstructorArgumentValues constructorValues = beanDefinition.getConstructorArgumentValues();
174+
Map<Integer, ValueHolder> indexedValues = constructorValues.getIndexedArgumentValues();
175+
if (!indexedValues.isEmpty()) {
176+
indexedValues.forEach((index, valueHolder) -> {
175177
CodeBlock valueCode = generateValue(valueHolder.getName(), valueHolder.getValue());
176178
code.addStatement(
177179
"$L.getConstructorArgumentValues().addIndexedArgumentValue($L, $L)",
178180
BEAN_DEFINITION_VARIABLE, index, valueCode);
179181
});
180182
}
183+
List<ValueHolder> genericValues = constructorValues.getGenericArgumentValues();
184+
if (!genericValues.isEmpty()) {
185+
genericValues.forEach(valueHolder -> {
186+
String valueName = valueHolder.getName();
187+
CodeBlock valueCode = generateValue(valueName, valueHolder.getValue());
188+
if (valueName != null) {
189+
CodeBlock valueTypeCode = this.valueCodeGenerator.generateCode(valueHolder.getType());
190+
code.addStatement(
191+
"$L.getConstructorArgumentValues().addGenericArgumentValue(new $T($L, $L, $S))",
192+
BEAN_DEFINITION_VARIABLE, ValueHolder.class, valueCode, valueTypeCode, valueName);
193+
}
194+
else if (valueHolder.getType() != null) {
195+
code.addStatement("$L.getConstructorArgumentValues().addGenericArgumentValue($L, $S)",
196+
BEAN_DEFINITION_VARIABLE, valueCode, valueHolder.getType());
197+
198+
}
199+
else {
200+
code.addStatement("$L.getConstructorArgumentValues().addGenericArgumentValue($L)",
201+
BEAN_DEFINITION_VARIABLE, valueCode);
202+
}
203+
});
204+
}
181205
}
182206

183207
private void addPropertyValues(CodeBlock.Builder code, RootBeanDefinition beanDefinition) {

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

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@
2020
import java.lang.reflect.Executable;
2121
import java.lang.reflect.Method;
2222
import java.lang.reflect.Modifier;
23+
import java.lang.reflect.Parameter;
2324
import java.util.Arrays;
25+
import java.util.HashSet;
2426
import java.util.LinkedHashSet;
27+
import java.util.Map;
2528
import java.util.Set;
2629
import java.util.stream.Collectors;
2730

@@ -248,7 +251,7 @@ private AutowiredArguments resolveArguments(RegisteredBean registeredBean, Execu
248251
Assert.isTrue(this.shortcuts == null || this.shortcuts.length == resolved.length,
249252
() -> "'shortcuts' must contain " + resolved.length + " elements");
250253

251-
ConstructorArgumentValues argumentValues = resolveArgumentValues(registeredBean);
254+
ValueHolder[] argumentValues = resolveArgumentValues(registeredBean, executable);
252255
Set<String> autowiredBeanNames = new LinkedHashSet<>(resolved.length * 2);
253256
for (int i = startIndex; i < parameterCount; i++) {
254257
MethodParameter parameter = getMethodParameter(executable, i);
@@ -257,8 +260,9 @@ private AutowiredArguments resolveArguments(RegisteredBean registeredBean, Execu
257260
if (shortcut != null) {
258261
descriptor = new ShortcutDependencyDescriptor(descriptor, shortcut);
259262
}
260-
ValueHolder argumentValue = argumentValues.getIndexedArgumentValue(i, null);
261-
resolved[i - startIndex] = resolveArgument(registeredBean, descriptor, argumentValue, autowiredBeanNames);
263+
ValueHolder argumentValue = argumentValues[i];
264+
resolved[i - startIndex] = resolveAutowiredArgument(
265+
registeredBean, descriptor, argumentValue, autowiredBeanNames);
262266
}
263267
registerDependentBeans(registeredBean.getBeanFactory(), registeredBean.getBeanName(), autowiredBeanNames);
264268

@@ -275,22 +279,44 @@ private MethodParameter getMethodParameter(Executable executable, int index) {
275279
throw new IllegalStateException("Unsupported executable: " + executable.getClass().getName());
276280
}
277281

278-
private ConstructorArgumentValues resolveArgumentValues(RegisteredBean registeredBean) {
279-
ConstructorArgumentValues resolved = new ConstructorArgumentValues();
282+
private ValueHolder[] resolveArgumentValues(RegisteredBean registeredBean, Executable executable) {
283+
Parameter[] parameters = executable.getParameters();
284+
ValueHolder[] resolved = new ValueHolder[parameters.length];
280285
RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition();
281286
if (beanDefinition.hasConstructorArgumentValues() &&
282287
registeredBean.getBeanFactory() instanceof AbstractAutowireCapableBeanFactory beanFactory) {
283288
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(
284289
beanFactory, registeredBean.getBeanName(), beanDefinition, beanFactory.getTypeConverter());
285-
ConstructorArgumentValues values = beanDefinition.getConstructorArgumentValues();
286-
values.getIndexedArgumentValues().forEach((index, valueHolder) -> {
287-
ValueHolder resolvedValue = resolveArgumentValue(valueResolver, valueHolder);
288-
resolved.addIndexedArgumentValue(index, resolvedValue);
289-
});
290+
ConstructorArgumentValues values = resolveConstructorArguments(
291+
valueResolver, beanDefinition.getConstructorArgumentValues());
292+
Set<ValueHolder> usedValueHolders = new HashSet<>(parameters.length);
293+
for (int i = 0; i < parameters.length; i++) {
294+
Class<?> parameterType = parameters[i].getType();
295+
String parameterName = (parameters[i].isNamePresent() ? parameters[i].getName() : null);
296+
ValueHolder valueHolder = values.getArgumentValue(
297+
i, parameterType, parameterName, usedValueHolders);
298+
if (valueHolder != null) {
299+
resolved[i] = valueHolder;
300+
usedValueHolders.add(valueHolder);
301+
}
302+
}
290303
}
291304
return resolved;
292305
}
293306

307+
private ConstructorArgumentValues resolveConstructorArguments(
308+
BeanDefinitionValueResolver valueResolver, ConstructorArgumentValues constructorArguments) {
309+
310+
ConstructorArgumentValues resolvedConstructorArguments = new ConstructorArgumentValues();
311+
for (Map.Entry<Integer, ConstructorArgumentValues.ValueHolder> entry : constructorArguments.getIndexedArgumentValues().entrySet()) {
312+
resolvedConstructorArguments.addIndexedArgumentValue(entry.getKey(), resolveArgumentValue(valueResolver, entry.getValue()));
313+
}
314+
for (ConstructorArgumentValues.ValueHolder valueHolder : constructorArguments.getGenericArgumentValues()) {
315+
resolvedConstructorArguments.addGenericArgumentValue(resolveArgumentValue(valueResolver, valueHolder));
316+
}
317+
return resolvedConstructorArguments;
318+
}
319+
294320
private ValueHolder resolveArgumentValue(BeanDefinitionValueResolver resolver, ValueHolder valueHolder) {
295321
if (valueHolder.isConverted()) {
296322
return valueHolder;
@@ -302,7 +328,7 @@ private ValueHolder resolveArgumentValue(BeanDefinitionValueResolver resolver, V
302328
}
303329

304330
@Nullable
305-
private Object resolveArgument(RegisteredBean registeredBean, DependencyDescriptor descriptor,
331+
private Object resolveAutowiredArgument(RegisteredBean registeredBean, DependencyDescriptor descriptor,
306332
@Nullable ValueHolder argumentValue, Set<String> autowiredBeanNames) {
307333

308334
TypeConverter typeConverter = registeredBean.getBeanFactory().getTypeConverter();

spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
6262
import org.springframework.beans.factory.config.DependencyDescriptor;
6363
import org.springframework.beans.factory.config.RuntimeBeanReference;
64+
import org.springframework.beans.factory.config.TypedStringValue;
6465
import org.springframework.core.CollectionFactory;
6566
import org.springframework.core.MethodParameter;
6667
import org.springframework.core.NamedThreadLocal;
@@ -999,6 +1000,9 @@ private List<ResolvableType> determineParameterValueTypes(RootBeanDefinition mbd
9991000
for (ValueHolder valueHolder : mbd.getConstructorArgumentValues().getIndexedArgumentValues().values()) {
10001001
parameterTypes.add(determineParameterValueType(mbd, valueHolder));
10011002
}
1003+
for (ValueHolder valueHolder : mbd.getConstructorArgumentValues().getGenericArgumentValues()) {
1004+
parameterTypes.add(determineParameterValueType(mbd, valueHolder));
1005+
}
10021006
return parameterTypes;
10031007
}
10041008

@@ -1023,6 +1027,12 @@ private ResolvableType determineParameterValueType(RootBeanDefinition mbd, Value
10231027
return (FactoryBean.class.isAssignableFrom(type.toClass()) ?
10241028
type.as(FactoryBean.class).getGeneric(0) : type);
10251029
}
1030+
if (value instanceof TypedStringValue typedValue) {
1031+
if (typedValue.hasTargetType()) {
1032+
return ResolvableType.forClass(typedValue.getTargetType());
1033+
}
1034+
return ResolvableType.forClass(String.class);
1035+
}
10261036
if (value instanceof Class<?> clazz) {
10271037
return ResolvableType.forClassWithGenerics(Class.class, clazz);
10281038
}

spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.springframework.beans.factory.FactoryBean;
4141
import org.springframework.beans.factory.config.BeanDefinition;
4242
import org.springframework.beans.factory.config.BeanReference;
43+
import org.springframework.beans.factory.config.ConstructorArgumentValues;
4344
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
4445
import org.springframework.beans.factory.config.RuntimeBeanNameReference;
4546
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -219,18 +220,49 @@ void setRoleWhenOther() {
219220
}
220221

221222
@Test
222-
void constructorArgumentValuesWhenValues() {
223+
void constructorArgumentValuesWhenIndexedValues() {
223224
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, String.class);
224225
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1, "test");
225226
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(2, 123);
226227
compile((actual, compiled) -> {
227-
Map<Integer, ValueHolder> values = actual.getConstructorArgumentValues().getIndexedArgumentValues();
228-
assertThat(values.get(0).getValue()).isEqualTo(String.class);
229-
assertThat(values.get(1).getValue()).isEqualTo("test");
230-
assertThat(values.get(2).getValue()).isEqualTo(123);
228+
ConstructorArgumentValues argumentValues = actual.getConstructorArgumentValues();
229+
Map<Integer, ValueHolder> values = argumentValues.getIndexedArgumentValues();
230+
assertThat(values.get(0)).satisfies(assertValueHolder(String.class, null, null));
231+
assertThat(values.get(1)).satisfies(assertValueHolder("test", null, null));
232+
assertThat(values.get(2)).satisfies(assertValueHolder(123, null, null));
233+
assertThat(values).hasSize(3);
234+
assertThat(argumentValues.getGenericArgumentValues()).isEmpty();
231235
});
232236
}
233237

238+
@Test
239+
void constructorArgumentValuesWhenGenericValuesWithName() {
240+
this.beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(String.class);
241+
this.beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(2, Long.class.getName());
242+
this.beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(
243+
new ValueHolder("value", null, "param1"));
244+
this.beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(
245+
new ValueHolder("another", CharSequence.class.getName(), "param2"));
246+
compile((actual, compiled) -> {
247+
ConstructorArgumentValues argumentValues = actual.getConstructorArgumentValues();
248+
List<ValueHolder> values = argumentValues.getGenericArgumentValues();
249+
assertThat(values.get(0)).satisfies(assertValueHolder(String.class, null, null));
250+
assertThat(values.get(1)).satisfies(assertValueHolder(2, Long.class, null));
251+
assertThat(values.get(2)).satisfies(assertValueHolder("value", null, "param1"));
252+
assertThat(values.get(3)).satisfies(assertValueHolder("another", CharSequence.class, "param2"));
253+
assertThat(values).hasSize(4);
254+
assertThat(argumentValues.getIndexedArgumentValues()).isEmpty();
255+
});
256+
}
257+
258+
private Consumer<ValueHolder> assertValueHolder(Object value, @Nullable Class<?> type, @Nullable String name) {
259+
return valueHolder -> {
260+
assertThat(valueHolder.getValue()).isEqualTo(value);
261+
assertThat(valueHolder.getType()).isEqualTo((type != null ? type.getName() : null));
262+
assertThat(valueHolder.getName()).isEqualTo(name);
263+
};
264+
}
265+
234266
@Test
235267
void propertyValuesWhenValues() {
236268
this.beanDefinition.setTargetType(PropertyValuesBean.class);

0 commit comments

Comments
 (0)