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
This commit is contained in:
Stéphane Nicoll 2023-10-12 15:01:28 +02:00
parent ca4d0d784b
commit 85388aa642
11 changed files with 445 additions and 57 deletions

View File

@ -24,6 +24,7 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@ -43,6 +44,7 @@ import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.AutowireCandidateQualifier; import org.springframework.beans.factory.support.AutowireCandidateQualifier;
@ -168,16 +170,38 @@ class BeanDefinitionPropertiesCodeGenerator {
} }
private void addConstructorArgumentValues(CodeBlock.Builder code, BeanDefinition beanDefinition) { private void addConstructorArgumentValues(CodeBlock.Builder code, BeanDefinition beanDefinition) {
Map<Integer, ValueHolder> argumentValues = ConstructorArgumentValues constructorValues = beanDefinition.getConstructorArgumentValues();
beanDefinition.getConstructorArgumentValues().getIndexedArgumentValues(); Map<Integer, ValueHolder> indexedValues = constructorValues.getIndexedArgumentValues();
if (!argumentValues.isEmpty()) { if (!indexedValues.isEmpty()) {
argumentValues.forEach((index, valueHolder) -> { indexedValues.forEach((index, valueHolder) -> {
CodeBlock valueCode = generateValue(valueHolder.getName(), valueHolder.getValue()); CodeBlock valueCode = generateValue(valueHolder.getName(), valueHolder.getValue());
code.addStatement( code.addStatement(
"$L.getConstructorArgumentValues().addIndexedArgumentValue($L, $L)", "$L.getConstructorArgumentValues().addIndexedArgumentValue($L, $L)",
BEAN_DEFINITION_VARIABLE, index, valueCode); BEAN_DEFINITION_VARIABLE, index, valueCode);
}); });
} }
List<ValueHolder> genericValues = constructorValues.getGenericArgumentValues();
if (!genericValues.isEmpty()) {
genericValues.forEach(valueHolder -> {
String valueName = valueHolder.getName();
CodeBlock valueCode = generateValue(valueName, valueHolder.getValue());
if (valueName != null) {
CodeBlock valueTypeCode = this.valueCodeGenerator.generateCode(valueHolder.getType());
code.addStatement(
"$L.getConstructorArgumentValues().addGenericArgumentValue(new $T($L, $L, $S))",
BEAN_DEFINITION_VARIABLE, ValueHolder.class, valueCode, valueTypeCode, valueName);
}
else if (valueHolder.getType() != null) {
code.addStatement("$L.getConstructorArgumentValues().addGenericArgumentValue($L, $S)",
BEAN_DEFINITION_VARIABLE, valueCode, valueHolder.getType());
}
else {
code.addStatement("$L.getConstructorArgumentValues().addGenericArgumentValue($L)",
BEAN_DEFINITION_VARIABLE, valueCode);
}
});
}
} }
private void addPropertyValues(CodeBlock.Builder code, RootBeanDefinition beanDefinition) { private void addPropertyValues(CodeBlock.Builder code, RootBeanDefinition beanDefinition) {

View File

@ -20,8 +20,11 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Executable; import java.lang.reflect.Executable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -248,7 +251,7 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
Assert.isTrue(this.shortcuts == null || this.shortcuts.length == resolved.length, Assert.isTrue(this.shortcuts == null || this.shortcuts.length == resolved.length,
() -> "'shortcuts' must contain " + resolved.length + " elements"); () -> "'shortcuts' must contain " + resolved.length + " elements");
ConstructorArgumentValues argumentValues = resolveArgumentValues(registeredBean); ValueHolder[] argumentValues = resolveArgumentValues(registeredBean, executable);
Set<String> autowiredBeanNames = new LinkedHashSet<>(resolved.length * 2); Set<String> autowiredBeanNames = new LinkedHashSet<>(resolved.length * 2);
for (int i = startIndex; i < parameterCount; i++) { for (int i = startIndex; i < parameterCount; i++) {
MethodParameter parameter = getMethodParameter(executable, i); MethodParameter parameter = getMethodParameter(executable, i);
@ -257,8 +260,9 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
if (shortcut != null) { if (shortcut != null) {
descriptor = new ShortcutDependencyDescriptor(descriptor, shortcut); descriptor = new ShortcutDependencyDescriptor(descriptor, shortcut);
} }
ValueHolder argumentValue = argumentValues.getIndexedArgumentValue(i, null); ValueHolder argumentValue = argumentValues[i];
resolved[i - startIndex] = resolveArgument(registeredBean, descriptor, argumentValue, autowiredBeanNames); resolved[i - startIndex] = resolveAutowiredArgument(
registeredBean, descriptor, argumentValue, autowiredBeanNames);
} }
registerDependentBeans(registeredBean.getBeanFactory(), registeredBean.getBeanName(), autowiredBeanNames); registerDependentBeans(registeredBean.getBeanFactory(), registeredBean.getBeanName(), autowiredBeanNames);
@ -275,22 +279,44 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
throw new IllegalStateException("Unsupported executable: " + executable.getClass().getName()); throw new IllegalStateException("Unsupported executable: " + executable.getClass().getName());
} }
private ConstructorArgumentValues resolveArgumentValues(RegisteredBean registeredBean) { private ValueHolder[] resolveArgumentValues(RegisteredBean registeredBean, Executable executable) {
ConstructorArgumentValues resolved = new ConstructorArgumentValues(); Parameter[] parameters = executable.getParameters();
ValueHolder[] resolved = new ValueHolder[parameters.length];
RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition(); RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition();
if (beanDefinition.hasConstructorArgumentValues() && if (beanDefinition.hasConstructorArgumentValues() &&
registeredBean.getBeanFactory() instanceof AbstractAutowireCapableBeanFactory beanFactory) { registeredBean.getBeanFactory() instanceof AbstractAutowireCapableBeanFactory beanFactory) {
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver( BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(
beanFactory, registeredBean.getBeanName(), beanDefinition, beanFactory.getTypeConverter()); beanFactory, registeredBean.getBeanName(), beanDefinition, beanFactory.getTypeConverter());
ConstructorArgumentValues values = beanDefinition.getConstructorArgumentValues(); ConstructorArgumentValues values = resolveConstructorArguments(
values.getIndexedArgumentValues().forEach((index, valueHolder) -> { valueResolver, beanDefinition.getConstructorArgumentValues());
ValueHolder resolvedValue = resolveArgumentValue(valueResolver, valueHolder); Set<ValueHolder> usedValueHolders = new HashSet<>(parameters.length);
resolved.addIndexedArgumentValue(index, resolvedValue); for (int i = 0; i < parameters.length; i++) {
}); Class<?> parameterType = parameters[i].getType();
String parameterName = (parameters[i].isNamePresent() ? parameters[i].getName() : null);
ValueHolder valueHolder = values.getArgumentValue(
i, parameterType, parameterName, usedValueHolders);
if (valueHolder != null) {
resolved[i] = valueHolder;
usedValueHolders.add(valueHolder);
}
}
} }
return resolved; return resolved;
} }
private ConstructorArgumentValues resolveConstructorArguments(
BeanDefinitionValueResolver valueResolver, ConstructorArgumentValues constructorArguments) {
ConstructorArgumentValues resolvedConstructorArguments = new ConstructorArgumentValues();
for (Map.Entry<Integer, ConstructorArgumentValues.ValueHolder> entry : constructorArguments.getIndexedArgumentValues().entrySet()) {
resolvedConstructorArguments.addIndexedArgumentValue(entry.getKey(), resolveArgumentValue(valueResolver, entry.getValue()));
}
for (ConstructorArgumentValues.ValueHolder valueHolder : constructorArguments.getGenericArgumentValues()) {
resolvedConstructorArguments.addGenericArgumentValue(resolveArgumentValue(valueResolver, valueHolder));
}
return resolvedConstructorArguments;
}
private ValueHolder resolveArgumentValue(BeanDefinitionValueResolver resolver, ValueHolder valueHolder) { private ValueHolder resolveArgumentValue(BeanDefinitionValueResolver resolver, ValueHolder valueHolder) {
if (valueHolder.isConverted()) { if (valueHolder.isConverted()) {
return valueHolder; return valueHolder;
@ -302,7 +328,7 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
} }
@Nullable @Nullable
private Object resolveArgument(RegisteredBean registeredBean, DependencyDescriptor descriptor, private Object resolveAutowiredArgument(RegisteredBean registeredBean, DependencyDescriptor descriptor,
@Nullable ValueHolder argumentValue, Set<String> autowiredBeanNames) { @Nullable ValueHolder argumentValue, Set<String> autowiredBeanNames) {
TypeConverter typeConverter = registeredBean.getBeanFactory().getTypeConverter(); TypeConverter typeConverter = registeredBean.getBeanFactory().getTypeConverter();

View File

@ -61,6 +61,7 @@ import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.core.CollectionFactory; import org.springframework.core.CollectionFactory;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.NamedThreadLocal; import org.springframework.core.NamedThreadLocal;
@ -999,6 +1000,9 @@ class ConstructorResolver {
for (ValueHolder valueHolder : mbd.getConstructorArgumentValues().getIndexedArgumentValues().values()) { for (ValueHolder valueHolder : mbd.getConstructorArgumentValues().getIndexedArgumentValues().values()) {
parameterTypes.add(determineParameterValueType(mbd, valueHolder)); parameterTypes.add(determineParameterValueType(mbd, valueHolder));
} }
for (ValueHolder valueHolder : mbd.getConstructorArgumentValues().getGenericArgumentValues()) {
parameterTypes.add(determineParameterValueType(mbd, valueHolder));
}
return parameterTypes; return parameterTypes;
} }
@ -1023,6 +1027,12 @@ class ConstructorResolver {
return (FactoryBean.class.isAssignableFrom(type.toClass()) ? return (FactoryBean.class.isAssignableFrom(type.toClass()) ?
type.as(FactoryBean.class).getGeneric(0) : type); type.as(FactoryBean.class).getGeneric(0) : type);
} }
if (value instanceof TypedStringValue typedValue) {
if (typedValue.hasTargetType()) {
return ResolvableType.forClass(typedValue.getTargetType());
}
return ResolvableType.forClass(String.class);
}
if (value instanceof Class<?> clazz) { if (value instanceof Class<?> clazz) {
return ResolvableType.forClassWithGenerics(Class.class, clazz); return ResolvableType.forClassWithGenerics(Class.class, clazz);
} }

View File

@ -40,6 +40,7 @@ import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.config.RuntimeBeanNameReference; import org.springframework.beans.factory.config.RuntimeBeanNameReference;
import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference;
@ -219,18 +220,49 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
} }
@Test @Test
void constructorArgumentValuesWhenValues() { void constructorArgumentValuesWhenIndexedValues() {
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, String.class); this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, String.class);
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1, "test"); this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1, "test");
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(2, 123); this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(2, 123);
compile((actual, compiled) -> { compile((actual, compiled) -> {
Map<Integer, ValueHolder> values = actual.getConstructorArgumentValues().getIndexedArgumentValues(); ConstructorArgumentValues argumentValues = actual.getConstructorArgumentValues();
assertThat(values.get(0).getValue()).isEqualTo(String.class); Map<Integer, ValueHolder> values = argumentValues.getIndexedArgumentValues();
assertThat(values.get(1).getValue()).isEqualTo("test"); assertThat(values.get(0)).satisfies(assertValueHolder(String.class, null, null));
assertThat(values.get(2).getValue()).isEqualTo(123); assertThat(values.get(1)).satisfies(assertValueHolder("test", null, null));
assertThat(values.get(2)).satisfies(assertValueHolder(123, null, null));
assertThat(values).hasSize(3);
assertThat(argumentValues.getGenericArgumentValues()).isEmpty();
}); });
} }
@Test
void constructorArgumentValuesWhenGenericValuesWithName() {
this.beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(String.class);
this.beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(2, Long.class.getName());
this.beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(
new ValueHolder("value", null, "param1"));
this.beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(
new ValueHolder("another", CharSequence.class.getName(), "param2"));
compile((actual, compiled) -> {
ConstructorArgumentValues argumentValues = actual.getConstructorArgumentValues();
List<ValueHolder> values = argumentValues.getGenericArgumentValues();
assertThat(values.get(0)).satisfies(assertValueHolder(String.class, null, null));
assertThat(values.get(1)).satisfies(assertValueHolder(2, Long.class, null));
assertThat(values.get(2)).satisfies(assertValueHolder("value", null, "param1"));
assertThat(values.get(3)).satisfies(assertValueHolder("another", CharSequence.class, "param2"));
assertThat(values).hasSize(4);
assertThat(argumentValues.getIndexedArgumentValues()).isEmpty();
});
}
private Consumer<ValueHolder> assertValueHolder(Object value, @Nullable Class<?> type, @Nullable String name) {
return valueHolder -> {
assertThat(valueHolder.getValue()).isEqualTo(value);
assertThat(valueHolder.getType()).isEqualTo((type != null ? type.getName() : null));
assertThat(valueHolder.getName()).isEqualTo(name);
};
}
@Test @Test
void propertyValuesWhenValues() { void propertyValuesWhenValues() {
this.beanDefinition.setTargetType(PropertyValuesBean.class); this.beanDefinition.setTargetType(PropertyValuesBean.class);

View File

@ -444,7 +444,7 @@ class BeanInstanceSupplierTests {
} }
@ParameterizedResolverTest(Sources.MIXED_ARGS) @ParameterizedResolverTest(Sources.MIXED_ARGS)
void resolveArgumentsWithMixedArgsConstructorWithUserValue(Source source) { void resolveArgumentsWithMixedArgsConstructorWithIndexedUserValue(Source source) {
ResourceLoader resourceLoader = new DefaultResourceLoader(); ResourceLoader resourceLoader = new DefaultResourceLoader();
Environment environment = mock(); Environment environment = mock();
this.beanFactory.registerResolvableDependency(ResourceLoader.class, this.beanFactory.registerResolvableDependency(ResourceLoader.class,
@ -465,7 +465,28 @@ class BeanInstanceSupplierTests {
} }
@ParameterizedResolverTest(Sources.MIXED_ARGS) @ParameterizedResolverTest(Sources.MIXED_ARGS)
void resolveArgumentsWithMixedArgsConstructorWithUserBeanReference(Source source) { void resolveArgumentsWithMixedArgsConstructorWithGenericUserValue(Source source) {
ResourceLoader resourceLoader = new DefaultResourceLoader();
Environment environment = mock();
this.beanFactory.registerResolvableDependency(ResourceLoader.class,
resourceLoader);
this.beanFactory.registerSingleton("environment", environment);
RegisteredBean registerBean = source.registerBean(this.beanFactory,
beanDefinition -> {
beanDefinition
.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
beanDefinition.getConstructorArgumentValues()
.addGenericArgumentValue("user-value");
});
AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(3);
assertThat(arguments.getObject(0)).isEqualTo(resourceLoader);
assertThat(arguments.getObject(1)).isEqualTo("user-value");
assertThat(arguments.getObject(2)).isEqualTo(environment);
}
@ParameterizedResolverTest(Sources.MIXED_ARGS)
void resolveArgumentsWithMixedArgsConstructorAndIndexedUserBeanReference(Source source) {
ResourceLoader resourceLoader = new DefaultResourceLoader(); ResourceLoader resourceLoader = new DefaultResourceLoader();
Environment environment = mock(); Environment environment = mock();
this.beanFactory.registerResolvableDependency(ResourceLoader.class, this.beanFactory.registerResolvableDependency(ResourceLoader.class,
@ -487,8 +508,31 @@ class BeanInstanceSupplierTests {
assertThat(arguments.getObject(2)).isEqualTo(environment); assertThat(arguments.getObject(2)).isEqualTo(environment);
} }
@ParameterizedResolverTest(Sources.MIXED_ARGS)
void resolveArgumentsWithMixedArgsConstructorAndGenericUserBeanReference(Source source) {
ResourceLoader resourceLoader = new DefaultResourceLoader();
Environment environment = mock();
this.beanFactory.registerResolvableDependency(ResourceLoader.class,
resourceLoader);
this.beanFactory.registerSingleton("environment", environment);
this.beanFactory.registerSingleton("one", "1");
this.beanFactory.registerSingleton("two", "2");
RegisteredBean registerBean = source.registerBean(this.beanFactory,
beanDefinition -> {
beanDefinition
.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
beanDefinition.getConstructorArgumentValues()
.addGenericArgumentValue(new RuntimeBeanReference("two"));
});
AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(3);
assertThat(arguments.getObject(0)).isEqualTo(resourceLoader);
assertThat(arguments.getObject(1)).isEqualTo("2");
assertThat(arguments.getObject(2)).isEqualTo(environment);
}
@Test @Test
void resolveArgumentsWithUserValueWithTypeConversionRequired() { void resolveIndexedArgumentsWithUserValueWithTypeConversionRequired() {
Source source = new Source(CharDependency.class, Source source = new Source(CharDependency.class,
BeanInstanceSupplier.forConstructor(char.class)); BeanInstanceSupplier.forConstructor(char.class));
RegisteredBean registerBean = source.registerBean(this.beanFactory, RegisteredBean registerBean = source.registerBean(this.beanFactory,
@ -503,8 +547,24 @@ class BeanInstanceSupplierTests {
assertThat(arguments.getObject(0)).isInstanceOf(Character.class).isEqualTo('\\'); assertThat(arguments.getObject(0)).isInstanceOf(Character.class).isEqualTo('\\');
} }
@Test
void resolveGenericArgumentsWithUserValueWithTypeConversionRequired() {
Source source = new Source(CharDependency.class,
BeanInstanceSupplier.forConstructor(char.class));
RegisteredBean registerBean = source.registerBean(this.beanFactory,
beanDefinition -> {
beanDefinition
.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
beanDefinition.getConstructorArgumentValues()
.addGenericArgumentValue("\\", char.class.getName());
});
AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(1);
assertThat(arguments.getObject(0)).isInstanceOf(Character.class).isEqualTo('\\');
}
@ParameterizedResolverTest(Sources.SINGLE_ARG) @ParameterizedResolverTest(Sources.SINGLE_ARG)
void resolveArgumentsWithUserValueWithBeanReference(Source source) { void resolveIndexedArgumentsWithUserValueWithBeanReference(Source source) {
this.beanFactory.registerSingleton("stringBean", "string"); this.beanFactory.registerSingleton("stringBean", "string");
RegisteredBean registerBean = source.registerBean(this.beanFactory, RegisteredBean registerBean = source.registerBean(this.beanFactory,
beanDefinition -> beanDefinition.getConstructorArgumentValues() beanDefinition -> beanDefinition.getConstructorArgumentValues()
@ -516,7 +576,18 @@ class BeanInstanceSupplierTests {
} }
@ParameterizedResolverTest(Sources.SINGLE_ARG) @ParameterizedResolverTest(Sources.SINGLE_ARG)
void resolveArgumentsWithUserValueWithBeanDefinition(Source source) { void resolveGenericArgumentsWithUserValueWithBeanReference(Source source) {
this.beanFactory.registerSingleton("stringBean", "string");
RegisteredBean registerBean = source.registerBean(this.beanFactory,
beanDefinition -> beanDefinition.getConstructorArgumentValues()
.addGenericArgumentValue(new RuntimeBeanReference("stringBean")));
AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(1);
assertThat(arguments.getObject(0)).isEqualTo("string");
}
@ParameterizedResolverTest(Sources.SINGLE_ARG)
void resolveIndexedArgumentsWithUserValueWithBeanDefinition(Source source) {
AbstractBeanDefinition userValue = BeanDefinitionBuilder AbstractBeanDefinition userValue = BeanDefinitionBuilder
.rootBeanDefinition(String.class, () -> "string").getBeanDefinition(); .rootBeanDefinition(String.class, () -> "string").getBeanDefinition();
RegisteredBean registerBean = source.registerBean(this.beanFactory, RegisteredBean registerBean = source.registerBean(this.beanFactory,
@ -528,11 +599,23 @@ class BeanInstanceSupplierTests {
} }
@ParameterizedResolverTest(Sources.SINGLE_ARG) @ParameterizedResolverTest(Sources.SINGLE_ARG)
void resolveArgumentsWithUserValueThatIsAlreadyResolved(Source source) { void resolveGenericArgumentsWithUserValueWithBeanDefinition(Source source) {
AbstractBeanDefinition userValue = BeanDefinitionBuilder
.rootBeanDefinition(String.class, () -> "string").getBeanDefinition();
RegisteredBean registerBean = source.registerBean(this.beanFactory,
beanDefinition -> beanDefinition.getConstructorArgumentValues()
.addGenericArgumentValue(userValue));
AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(1);
assertThat(arguments.getObject(0)).isEqualTo("string");
}
@ParameterizedResolverTest(Sources.SINGLE_ARG)
void resolveIndexedArgumentsWithUserValueThatIsAlreadyResolved(Source source) {
RegisteredBean registerBean = source.registerBean(this.beanFactory); RegisteredBean registerBean = source.registerBean(this.beanFactory);
BeanDefinition mergedBeanDefinition = this.beanFactory BeanDefinition mergedBeanDefinition = this.beanFactory
.getMergedBeanDefinition("testBean"); .getMergedBeanDefinition("testBean");
ValueHolder valueHolder = new ValueHolder('a'); ValueHolder valueHolder = new ValueHolder("a");
valueHolder.setConvertedValue("this is an a"); valueHolder.setConvertedValue("this is an a");
mergedBeanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, mergedBeanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
valueHolder); valueHolder);
@ -541,6 +624,19 @@ class BeanInstanceSupplierTests {
assertThat(arguments.getObject(0)).isEqualTo("this is an a"); assertThat(arguments.getObject(0)).isEqualTo("this is an a");
} }
@ParameterizedResolverTest(Sources.SINGLE_ARG)
void resolveGenericArgumentsWithUserValueThatIsAlreadyResolved(Source source) {
RegisteredBean registerBean = source.registerBean(this.beanFactory);
BeanDefinition mergedBeanDefinition = this.beanFactory
.getMergedBeanDefinition("testBean");
ValueHolder valueHolder = new ValueHolder("a");
valueHolder.setConvertedValue("this is an a");
mergedBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
AutowiredArguments arguments = source.getResolver().resolveArguments(registerBean);
assertThat(arguments.toArray()).hasSize(1);
assertThat(arguments.getObject(0)).isEqualTo("this is an a");
}
@Test @Test
void resolveArgumentsWhenUsingShortcutsInjectsDirectly() { void resolveArgumentsWhenUsingShortcutsInjectsDirectly() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory() { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory() {

View File

@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.testfixture.beans.factory.generator.factory.NumberHolder; import org.springframework.beans.testfixture.beans.factory.generator.factory.NumberHolder;
import org.springframework.beans.testfixture.beans.factory.generator.factory.NumberHolderFactoryBean; import org.springframework.beans.testfixture.beans.factory.generator.factory.NumberHolderFactoryBean;
import org.springframework.beans.testfixture.beans.factory.generator.factory.SampleFactory; import org.springframework.beans.testfixture.beans.factory.generator.factory.SampleFactory;
@ -72,7 +73,7 @@ class ConstructorResolverAotTests {
} }
@Test @Test
void beanDefinitionWithFactoryMethodNameAndAssignableConstructorArg() { void beanDefinitionWithFactoryMethodNameAndAssignableIndexedConstructorArgs() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("testNumber", 1L); beanFactory.registerSingleton("testNumber", 1L);
beanFactory.registerSingleton("testBean", "test"); beanFactory.registerSingleton("testBean", "test");
@ -85,6 +86,34 @@ class ConstructorResolverAotTests {
.findMethod(SampleFactory.class, "create", Number.class, String.class)); .findMethod(SampleFactory.class, "create", Number.class, String.class));
} }
@Test
void beanDefinitionWithFactoryMethodNameAndAssignableGenericConstructorArgs() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(SampleFactory.class).setFactoryMethod("create")
.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue("test");
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(1L);
Executable executable = resolve(beanFactory, beanDefinition);
assertThat(executable).isNotNull().isEqualTo(ReflectionUtils
.findMethod(SampleFactory.class, "create", Number.class, String.class));
}
@Test
void beanDefinitionWithFactoryMethodNameAndAssignableTypeStringValues() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(SampleFactory.class).setFactoryMethod("create")
.getBeanDefinition();
beanDefinition.getConstructorArgumentValues()
.addGenericArgumentValue(new TypedStringValue("test"));
beanDefinition.getConstructorArgumentValues()
.addGenericArgumentValue(new TypedStringValue("1", Integer.class));
Executable executable = resolve(beanFactory, beanDefinition);
assertThat(executable).isNotNull().isEqualTo(ReflectionUtils
.findMethod(SampleFactory.class, "create", Number.class, String.class));
}
@Test @Test
void beanDefinitionWithFactoryMethodNameAndMatchingMethodNames() { void beanDefinitionWithFactoryMethodNameAndMatchingMethodNames() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
@ -122,7 +151,7 @@ class ConstructorResolverAotTests {
} }
@Test @Test
void beanDefinitionWithConstructorArgsForMultipleConstructors() throws Exception { void beanDefinitionWithIndexedConstructorArgsForMultipleConstructors() throws Exception {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("testNumber", 1L); beanFactory.registerSingleton("testNumber", 1L);
beanFactory.registerSingleton("testBean", "test"); beanFactory.registerSingleton("testBean", "test");
@ -136,7 +165,22 @@ class ConstructorResolverAotTests {
} }
@Test @Test
void beanDefinitionWithMultiArgConstructorAndMatchingValue() throws NoSuchMethodException { void beanDefinitionWithGenericConstructorArgsForMultipleConstructors() throws Exception {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("testNumber", 1L);
beanFactory.registerSingleton("testBean", "test");
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(SampleBeanWithConstructors.class)
.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue("test");
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(1L);
Executable executable = resolve(beanFactory, beanDefinition);
assertThat(executable).isNotNull().isEqualTo(SampleBeanWithConstructors.class
.getDeclaredConstructor(Number.class, String.class));
}
@Test
void beanDefinitionWithMultiArgConstructorAndMatchingIndexedValue() throws NoSuchMethodException {
BeanDefinition beanDefinition = BeanDefinitionBuilder BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(MultiConstructorSample.class) .rootBeanDefinition(MultiConstructorSample.class)
.addConstructorArgValue(42).getBeanDefinition(); .addConstructorArgValue(42).getBeanDefinition();
@ -146,7 +190,18 @@ class ConstructorResolverAotTests {
} }
@Test @Test
void beanDefinitionWithMultiArgConstructorAndMatchingArrayValue() throws NoSuchMethodException { void beanDefinitionWithMultiArgConstructorAndMatchingGenericValue() throws NoSuchMethodException {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(MultiConstructorSample.class)
.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(42);
Executable executable = resolve(new DefaultListableBeanFactory(), beanDefinition);
assertThat(executable).isNotNull().isEqualTo(
MultiConstructorSample.class.getDeclaredConstructor(Integer.class));
}
@Test
void beanDefinitionWithMultiArgConstructorAndMatchingArrayFromIndexedValue() throws NoSuchMethodException {
BeanDefinition beanDefinition = BeanDefinitionBuilder BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(MultiConstructorArraySample.class) .rootBeanDefinition(MultiConstructorArraySample.class)
.addConstructorArgValue(42).getBeanDefinition(); .addConstructorArgValue(42).getBeanDefinition();
@ -156,7 +211,18 @@ class ConstructorResolverAotTests {
} }
@Test @Test
void beanDefinitionWithMultiArgConstructorAndMatchingListValue() throws NoSuchMethodException { void beanDefinitionWithMultiArgConstructorAndMatchingArrayFromGenericValue() throws NoSuchMethodException {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(MultiConstructorArraySample.class)
.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(42);
Executable executable = resolve(new DefaultListableBeanFactory(), beanDefinition);
assertThat(executable).isNotNull().isEqualTo(MultiConstructorArraySample.class
.getDeclaredConstructor(Integer[].class));
}
@Test
void beanDefinitionWithMultiArgConstructorAndMatchingListFromIndexedValue() throws NoSuchMethodException {
BeanDefinition beanDefinition = BeanDefinitionBuilder BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(MultiConstructorListSample.class) .rootBeanDefinition(MultiConstructorListSample.class)
.addConstructorArgValue(42).getBeanDefinition(); .addConstructorArgValue(42).getBeanDefinition();
@ -166,7 +232,18 @@ class ConstructorResolverAotTests {
} }
@Test @Test
void beanDefinitionWithMultiArgConstructorAndMatchingValueAsInnerBean() throws NoSuchMethodException { void beanDefinitionWithMultiArgConstructorAndMatchingListFromGenericValue() throws NoSuchMethodException {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(MultiConstructorListSample.class)
.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(42);
Executable executable = resolve(new DefaultListableBeanFactory(), beanDefinition);
assertThat(executable).isNotNull().isEqualTo(
MultiConstructorListSample.class.getDeclaredConstructor(List.class));
}
@Test
void beanDefinitionWithMultiArgConstructorAndMatchingIndexedValueAsInnerBean() throws NoSuchMethodException {
BeanDefinition beanDefinition = BeanDefinitionBuilder BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(MultiConstructorSample.class) .rootBeanDefinition(MultiConstructorSample.class)
.addConstructorArgValue( .addConstructorArgValue(
@ -179,7 +256,20 @@ class ConstructorResolverAotTests {
} }
@Test @Test
void beanDefinitionWithMultiArgConstructorAndMatchingValueAsInnerBeanFactory() throws NoSuchMethodException { void beanDefinitionWithMultiArgConstructorAndMatchingGenericValueAsInnerBean() throws NoSuchMethodException {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(MultiConstructorSample.class)
.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(
BeanDefinitionBuilder.rootBeanDefinition(Integer.class, "valueOf")
.addConstructorArgValue("42").getBeanDefinition());
Executable executable = resolve(new DefaultListableBeanFactory(), beanDefinition);
assertThat(executable).isNotNull().isEqualTo(
MultiConstructorSample.class.getDeclaredConstructor(Integer.class));
}
@Test
void beanDefinitionWithMultiArgConstructorAndMatchingIndexedValueAsInnerBeanFactory() throws NoSuchMethodException {
BeanDefinition beanDefinition = BeanDefinitionBuilder BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(MultiConstructorSample.class) .rootBeanDefinition(MultiConstructorSample.class)
.addConstructorArgValue(BeanDefinitionBuilder .addConstructorArgValue(BeanDefinitionBuilder
@ -190,6 +280,18 @@ class ConstructorResolverAotTests {
MultiConstructorSample.class.getDeclaredConstructor(Integer.class)); MultiConstructorSample.class.getDeclaredConstructor(Integer.class));
} }
@Test
void beanDefinitionWithMultiArgConstructorAndMatchingGenericValueAsInnerBeanFactory() throws NoSuchMethodException {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(MultiConstructorSample.class)
.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(
BeanDefinitionBuilder.rootBeanDefinition(IntegerFactoryBean.class).getBeanDefinition());
Executable executable = resolve(new DefaultListableBeanFactory(), beanDefinition);
assertThat(executable).isNotNull().isEqualTo(
MultiConstructorSample.class.getDeclaredConstructor(Integer.class));
}
@Test @Test
void beanDefinitionWithMultiArgConstructorAndNonMatchingValue() { void beanDefinitionWithMultiArgConstructorAndNonMatchingValue() {
BeanDefinition beanDefinition = BeanDefinitionBuilder BeanDefinition beanDefinition = BeanDefinitionBuilder

View File

@ -30,6 +30,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.PropertyValue; import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@ -479,26 +480,32 @@ final class PostProcessorRegistrationDelegate {
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this.beanFactory, beanName, bd); BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this.beanFactory, beanName, bd);
postProcessors.forEach(postProcessor -> postProcessor.postProcessMergedBeanDefinition(bd, beanType, beanName)); postProcessors.forEach(postProcessor -> postProcessor.postProcessMergedBeanDefinition(bd, beanType, beanName));
for (PropertyValue propertyValue : bd.getPropertyValues().getPropertyValueList()) { for (PropertyValue propertyValue : bd.getPropertyValues().getPropertyValueList()) {
Object value = propertyValue.getValue(); postProcessValue(postProcessors, valueResolver, propertyValue.getValue());
if (value instanceof AbstractBeanDefinition innerBd) {
Class<?> innerBeanType = resolveBeanType(innerBd);
resolveInnerBeanDefinition(valueResolver, innerBd, (innerBeanName, innerBeanDefinition)
-> postProcessRootBeanDefinition(postProcessors, innerBeanName, innerBeanType, innerBeanDefinition));
}
if (value instanceof TypedStringValue typedStringValue) {
resolveTypeStringValue(typedStringValue);
}
} }
for (ValueHolder valueHolder : bd.getConstructorArgumentValues().getIndexedArgumentValues().values()) { for (ValueHolder valueHolder : bd.getConstructorArgumentValues().getIndexedArgumentValues().values()) {
Object value = valueHolder.getValue(); postProcessValue(postProcessors, valueResolver, valueHolder.getValue());
if (value instanceof AbstractBeanDefinition innerBd) { }
for (ValueHolder valueHolder : bd.getConstructorArgumentValues().getGenericArgumentValues()) {
postProcessValue(postProcessors, valueResolver, valueHolder.getValue());
}
}
private void postProcessValue(List<MergedBeanDefinitionPostProcessor> postProcessors,
BeanDefinitionValueResolver valueResolver, @Nullable Object value) {
if (value instanceof BeanDefinitionHolder bdh
&& bdh.getBeanDefinition() instanceof AbstractBeanDefinition innerBd) {
Class<?> innerBeanType = resolveBeanType(innerBd); Class<?> innerBeanType = resolveBeanType(innerBd);
resolveInnerBeanDefinition(valueResolver, innerBd, (innerBeanName, innerBeanDefinition) resolveInnerBeanDefinition(valueResolver, innerBd, (innerBeanName, innerBeanDefinition)
-> postProcessRootBeanDefinition(postProcessors, innerBeanName, innerBeanType, innerBeanDefinition)); -> postProcessRootBeanDefinition(postProcessors, innerBeanName, innerBeanType, innerBeanDefinition));
} }
if (value instanceof TypedStringValue typedStringValue) { else if (value instanceof AbstractBeanDefinition innerBd) {
resolveTypeStringValue(typedStringValue); Class<?> innerBeanType = resolveBeanType(innerBd);
resolveInnerBeanDefinition(valueResolver, innerBd, (innerBeanName, innerBeanDefinition)
-> postProcessRootBeanDefinition(postProcessors, innerBeanName, innerBeanType, innerBeanDefinition));
} }
else if (value instanceof TypedStringValue typedStringValue) {
resolveTypeStringValue(typedStringValue);
} }
} }

View File

@ -459,8 +459,10 @@ class ApplicationContextAotGeneratorTests {
assertThat(employee.getName()).isEqualTo("John Smith"); assertThat(employee.getName()).isEqualTo("John Smith");
assertThat(employee.getAge()).isEqualTo(42); assertThat(employee.getAge()).isEqualTo(42);
assertThat(employee.getCompany()).isEqualTo("Acme Widgets, Inc."); assertThat(employee.getCompany()).isEqualTo("Acme Widgets, Inc.");
assertThat(freshApplicationContext.getBean("pet", Pet.class) assertThat(freshApplicationContext.getBean("petIndexed", Pet.class)
.getName()).isEqualTo("Fido"); .getName()).isEqualTo("Fido");
assertThat(freshApplicationContext.getBean("petGeneric", Pet.class)
.getName()).isEqualTo("Dofi");
}); });
} }
@ -496,6 +498,20 @@ class ApplicationContextAotGeneratorTests {
}); });
} }
@Test
void processAheadOfTimeWhenXmlHasBeanReferences() {
GenericXmlApplicationContext applicationContext = new GenericXmlApplicationContext();
applicationContext
.load(new ClassPathResource("applicationContextAotGeneratorTests-references.xml", getClass()));
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
assertThat(freshApplicationContext.getBean("petInnerBean", Pet.class)
.getName()).isEqualTo("Fido");
assertThat(freshApplicationContext.getBean("petRefBean", Pet.class)
.getName()).isEqualTo("Dofi");
});
}
} }
private Consumer<List<? extends JdkProxyHint>> doesNotHaveProxyFor(Class<?> target) { private Consumer<List<? extends JdkProxyHint>> doesNotHaveProxyFor(Class<?> target) {

View File

@ -331,7 +331,7 @@ class GenericApplicationContextTests {
} }
@Test @Test
void refreshForAotLoadsBeanClassNameOfConstructorArgumentInnerBeanDefinition() { void refreshForAotLoadsBeanClassNameOfIndexedConstructorArgumentInnerBeanDefinition() {
GenericApplicationContext context = new GenericApplicationContext(); GenericApplicationContext context = new GenericApplicationContext();
RootBeanDefinition beanDefinition = new RootBeanDefinition(String.class); RootBeanDefinition beanDefinition = new RootBeanDefinition(String.class);
GenericBeanDefinition innerBeanDefinition = new GenericBeanDefinition(); GenericBeanDefinition innerBeanDefinition = new GenericBeanDefinition();
@ -347,6 +347,23 @@ class GenericApplicationContextTests {
context.close(); context.close();
} }
@Test
void refreshForAotLoadsBeanClassNameOfGenericConstructorArgumentInnerBeanDefinition() {
GenericApplicationContext context = new GenericApplicationContext();
RootBeanDefinition beanDefinition = new RootBeanDefinition(String.class);
GenericBeanDefinition innerBeanDefinition = new GenericBeanDefinition();
innerBeanDefinition.setBeanClassName("java.lang.Integer");
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(innerBeanDefinition);
context.registerBeanDefinition("test",beanDefinition);
context.refreshForAotProcessing(new RuntimeHints());
RootBeanDefinition bd = getBeanDefinition(context, "test");
GenericBeanDefinition value = (GenericBeanDefinition) bd.getConstructorArgumentValues()
.getGenericArgumentValues().get(0).getValue();
assertThat(value.hasBeanClass()).isTrue();
assertThat(value.getBeanClass()).isEqualTo(Integer.class);
context.close();
}
@Test @Test
void refreshForAotLoadsBeanClassNameOfPropertyValueInnerBeanDefinition() { void refreshForAotLoadsBeanClassNameOfPropertyValueInnerBeanDefinition() {
GenericApplicationContext context = new GenericApplicationContext(); GenericApplicationContext context = new GenericApplicationContext();
@ -377,7 +394,7 @@ class GenericApplicationContextTests {
} }
@Test @Test
void refreshForAotLoadsTypedStringValueClassNameInConstructorArgument() { void refreshForAotLoadsTypedStringValueClassNameInIndexedConstructorArgument() {
GenericApplicationContext context = new GenericApplicationContext(); GenericApplicationContext context = new GenericApplicationContext();
RootBeanDefinition beanDefinition = new RootBeanDefinition("java.lang.Integer"); RootBeanDefinition beanDefinition = new RootBeanDefinition("java.lang.Integer");
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
@ -391,6 +408,21 @@ class GenericApplicationContextTests {
context.close(); context.close();
} }
@Test
void refreshForAotLoadsTypedStringValueClassNameInGenericConstructorArgument() {
GenericApplicationContext context = new GenericApplicationContext();
RootBeanDefinition beanDefinition = new RootBeanDefinition("java.lang.Integer");
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(
new TypedStringValue("42", "java.lang.Integer"));
context.registerBeanDefinition("number", beanDefinition);
context.refreshForAotProcessing(new RuntimeHints());
assertThat(getBeanDefinition(context, "number").getConstructorArgumentValues()
.getGenericArgumentValue(TypedStringValue.class).getValue())
.isInstanceOfSatisfying(TypedStringValue.class, typeStringValue ->
assertThat(typeStringValue.getTargetType()).isEqualTo(Integer.class));
context.close();
}
@Test @Test
void refreshForAotInvokesBeanFactoryPostProcessors() { void refreshForAotInvokesBeanFactoryPostProcessors() {
GenericApplicationContext context = new GenericApplicationContext(); GenericApplicationContext context = new GenericApplicationContext();
@ -414,7 +446,7 @@ class GenericApplicationContextTests {
} }
@Test @Test
void refreshForAotInvokesMergedBeanDefinitionPostProcessorsOnConstructorArgument() { void refreshForAotInvokesMergedBeanDefinitionPostProcessorsOnIndexedConstructorArgument() {
GenericApplicationContext context = new GenericApplicationContext(); GenericApplicationContext context = new GenericApplicationContext();
RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanD.class); RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanD.class);
GenericBeanDefinition innerBeanDefinition = new GenericBeanDefinition(); GenericBeanDefinition innerBeanDefinition = new GenericBeanDefinition();
@ -430,6 +462,23 @@ class GenericApplicationContextTests {
context.close(); context.close();
} }
@Test
void refreshForAotInvokesMergedBeanDefinitionPostProcessorsOnGenericConstructorArgument() {
GenericApplicationContext context = new GenericApplicationContext();
RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanD.class);
GenericBeanDefinition innerBeanDefinition = new GenericBeanDefinition();
innerBeanDefinition.setBeanClassName("java.lang.Integer");
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(innerBeanDefinition);
context.registerBeanDefinition("test", beanDefinition);
MergedBeanDefinitionPostProcessor bpp = registerMockMergedBeanDefinitionPostProcessor(context);
context.refreshForAotProcessing(new RuntimeHints());
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(bpp).postProcessMergedBeanDefinition(getBeanDefinition(context, "test"), BeanD.class, "test");
verify(bpp).postProcessMergedBeanDefinition(any(RootBeanDefinition.class), eq(Integer.class), captor.capture());
assertThat(captor.getValue()).startsWith("(inner bean)");
context.close();
}
@Test @Test
void refreshForAotInvokesMergedBeanDefinitionPostProcessorsOnPropertyValue() { void refreshForAotInvokesMergedBeanDefinitionPostProcessorsOnPropertyValue() {
GenericApplicationContext context = new GenericApplicationContext(); GenericApplicationContext context = new GenericApplicationContext();

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="petInnerBean" class="org.springframework.beans.testfixture.beans.Pet">
<constructor-arg>
<bean class="java.lang.String">
<constructor-arg value="Fido"/>
</bean>
</constructor-arg>
</bean>
<bean id="petName" class="java.lang.String">
<constructor-arg value="Dofi"/>
</bean>
<bean id="petRefBean" class="org.springframework.beans.testfixture.beans.Pet">
<constructor-arg ref="petName"/>
</bean>
</beans>

View File

@ -8,8 +8,12 @@
<property name="company" value="Acme Widgets, Inc." /> <property name="company" value="Acme Widgets, Inc." />
</bean> </bean>
<bean id="pet" class="org.springframework.beans.testfixture.beans.Pet"> <bean id="petIndexed" class="org.springframework.beans.testfixture.beans.Pet">
<constructor-arg index="0" value="Fido" /> <constructor-arg index="0" value="Fido" />
</bean> </bean>
<bean id="petGeneric" class="org.springframework.beans.testfixture.beans.Pet">
<constructor-arg value="Dofi" />
</bean>
</beans> </beans>