Skip to content

Commit

Permalink
Add AOT support for generic constructor argument values
Browse files Browse the repository at this point in the history
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
  • Loading branch information
snicoll committed Oct 13, 2023
1 parent ca4d0d7 commit 85388aa
Show file tree
Hide file tree
Showing 11 changed files with 445 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
Expand All @@ -43,6 +44,7 @@
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
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.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
Expand Down Expand Up @@ -168,16 +170,38 @@ private void addInitDestroyHint(Class<?> beanUserClass, String methodName) {
}

private void addConstructorArgumentValues(CodeBlock.Builder code, BeanDefinition beanDefinition) {
Map<Integer, ValueHolder> argumentValues =
beanDefinition.getConstructorArgumentValues().getIndexedArgumentValues();
if (!argumentValues.isEmpty()) {
argumentValues.forEach((index, valueHolder) -> {
ConstructorArgumentValues constructorValues = beanDefinition.getConstructorArgumentValues();
Map<Integer, ValueHolder> indexedValues = constructorValues.getIndexedArgumentValues();
if (!indexedValues.isEmpty()) {
indexedValues.forEach((index, valueHolder) -> {
CodeBlock valueCode = generateValue(valueHolder.getName(), valueHolder.getValue());
code.addStatement(
"$L.getConstructorArgumentValues().addIndexedArgumentValue($L, $L)",
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

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

ConstructorArgumentValues argumentValues = resolveArgumentValues(registeredBean);
ValueHolder[] argumentValues = resolveArgumentValues(registeredBean, executable);
Set<String> autowiredBeanNames = new LinkedHashSet<>(resolved.length * 2);
for (int i = startIndex; i < parameterCount; i++) {
MethodParameter parameter = getMethodParameter(executable, i);
Expand All @@ -257,8 +260,9 @@ private AutowiredArguments resolveArguments(RegisteredBean registeredBean, Execu
if (shortcut != null) {
descriptor = new ShortcutDependencyDescriptor(descriptor, shortcut);
}
ValueHolder argumentValue = argumentValues.getIndexedArgumentValue(i, null);
resolved[i - startIndex] = resolveArgument(registeredBean, descriptor, argumentValue, autowiredBeanNames);
ValueHolder argumentValue = argumentValues[i];
resolved[i - startIndex] = resolveAutowiredArgument(
registeredBean, descriptor, argumentValue, autowiredBeanNames);
}
registerDependentBeans(registeredBean.getBeanFactory(), registeredBean.getBeanName(), autowiredBeanNames);

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

private ConstructorArgumentValues resolveArgumentValues(RegisteredBean registeredBean) {
ConstructorArgumentValues resolved = new ConstructorArgumentValues();
private ValueHolder[] resolveArgumentValues(RegisteredBean registeredBean, Executable executable) {
Parameter[] parameters = executable.getParameters();
ValueHolder[] resolved = new ValueHolder[parameters.length];
RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition();
if (beanDefinition.hasConstructorArgumentValues() &&
registeredBean.getBeanFactory() instanceof AbstractAutowireCapableBeanFactory beanFactory) {
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(
beanFactory, registeredBean.getBeanName(), beanDefinition, beanFactory.getTypeConverter());
ConstructorArgumentValues values = beanDefinition.getConstructorArgumentValues();
values.getIndexedArgumentValues().forEach((index, valueHolder) -> {
ValueHolder resolvedValue = resolveArgumentValue(valueResolver, valueHolder);
resolved.addIndexedArgumentValue(index, resolvedValue);
});
ConstructorArgumentValues values = resolveConstructorArguments(
valueResolver, beanDefinition.getConstructorArgumentValues());
Set<ValueHolder> usedValueHolders = new HashSet<>(parameters.length);
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;
}

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) {
if (valueHolder.isConverted()) {
return valueHolder;
Expand All @@ -302,7 +328,7 @@ private ValueHolder resolveArgumentValue(BeanDefinitionValueResolver resolver, V
}

@Nullable
private Object resolveArgument(RegisteredBean registeredBean, DependencyDescriptor descriptor,
private Object resolveAutowiredArgument(RegisteredBean registeredBean, DependencyDescriptor descriptor,
@Nullable ValueHolder argumentValue, Set<String> autowiredBeanNames) {

TypeConverter typeConverter = registeredBean.getBeanFactory().getTypeConverter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.core.CollectionFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.NamedThreadLocal;
Expand Down Expand Up @@ -999,6 +1000,9 @@ private List<ResolvableType> determineParameterValueTypes(RootBeanDefinition mbd
for (ValueHolder valueHolder : mbd.getConstructorArgumentValues().getIndexedArgumentValues().values()) {
parameterTypes.add(determineParameterValueType(mbd, valueHolder));
}
for (ValueHolder valueHolder : mbd.getConstructorArgumentValues().getGenericArgumentValues()) {
parameterTypes.add(determineParameterValueType(mbd, valueHolder));
}
return parameterTypes;
}

Expand All @@ -1023,6 +1027,12 @@ private ResolvableType determineParameterValueType(RootBeanDefinition mbd, Value
return (FactoryBean.class.isAssignableFrom(type.toClass()) ?
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) {
return ResolvableType.forClassWithGenerics(Class.class, clazz);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
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.RuntimeBeanNameReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
Expand Down Expand Up @@ -219,18 +220,49 @@ void setRoleWhenOther() {
}

@Test
void constructorArgumentValuesWhenValues() {
void constructorArgumentValuesWhenIndexedValues() {
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, String.class);
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1, "test");
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(2, 123);
compile((actual, compiled) -> {
Map<Integer, ValueHolder> values = actual.getConstructorArgumentValues().getIndexedArgumentValues();
assertThat(values.get(0).getValue()).isEqualTo(String.class);
assertThat(values.get(1).getValue()).isEqualTo("test");
assertThat(values.get(2).getValue()).isEqualTo(123);
ConstructorArgumentValues argumentValues = actual.getConstructorArgumentValues();
Map<Integer, ValueHolder> values = argumentValues.getIndexedArgumentValues();
assertThat(values.get(0)).satisfies(assertValueHolder(String.class, null, null));
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
void propertyValuesWhenValues() {
this.beanDefinition.setTargetType(PropertyValuesBean.class);
Expand Down

0 comments on commit 85388aa

Please sign in to comment.