Add AOT support for TypedStringValue
This commit adds support for TypeStringValue when generating AOT code. If the value does not specify an explicit type, it's specified as is. Otherwise, the TypeStringValue instance is restored via the appropriate code generation. Closes gh-29074
This commit is contained in:
committed by
Stéphane Nicoll
parent
15c19411f6
commit
37c2619fc4
@@ -38,6 +38,7 @@ import org.springframework.aot.generate.GeneratedMethods;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanReference;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.config.TypedStringValue;
|
||||
import org.springframework.beans.factory.support.ManagedList;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.beans.factory.support.ManagedSet;
|
||||
@@ -88,7 +89,8 @@ class BeanDefinitionPropertyValueCodeGenerator {
|
||||
new ListDelegate(),
|
||||
new SetDelegate(),
|
||||
new MapDelegate(),
|
||||
new BeanReferenceDelegate()
|
||||
new BeanReferenceDelegate(),
|
||||
new TypedStringValueDelegate()
|
||||
));
|
||||
}
|
||||
|
||||
@@ -569,4 +571,31 @@ class BeanDefinitionPropertyValueCodeGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Delegate} for {@link TypedStringValue} types.
|
||||
*/
|
||||
private class TypedStringValueDelegate implements Delegate {
|
||||
|
||||
@Override
|
||||
public CodeBlock generateCode(Object value, ResolvableType type) {
|
||||
if (value instanceof TypedStringValue typedStringValue) {
|
||||
return generateTypeStringValueCode(typedStringValue);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private CodeBlock generateTypeStringValueCode(TypedStringValue typedStringValue) {
|
||||
String value = typedStringValue.getValue();
|
||||
if (typedStringValue.hasTargetType()) {
|
||||
return CodeBlock.of("new $T($S, $L)", TypedStringValue.class, value,
|
||||
generateCode(typedStringValue.getTargetType()));
|
||||
}
|
||||
return generateCode(value);
|
||||
}
|
||||
|
||||
private CodeBlock generateCode(@Nullable Object value) {
|
||||
return BeanDefinitionPropertyValueCodeGenerator.this.generateCode(value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.beans.factory.config;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -35,7 +37,7 @@ import org.springframework.util.ObjectUtils;
|
||||
* @see BeanDefinition#getPropertyValues
|
||||
* @see org.springframework.beans.MutablePropertyValues#addPropertyValue
|
||||
*/
|
||||
public class TypedStringValue implements BeanMetadataElement {
|
||||
public class TypedStringValue implements BeanMetadataElement, Comparable<TypedStringValue> {
|
||||
|
||||
@Nullable
|
||||
private String value;
|
||||
@@ -213,6 +215,10 @@ public class TypedStringValue implements BeanMetadataElement {
|
||||
return this.dynamic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@Nullable TypedStringValue o) {
|
||||
return Comparator.comparing(TypedStringValue::getValue).compare(this, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
|
||||
import org.springframework.beans.factory.config.TypedStringValue;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
@@ -311,8 +312,9 @@ final class PostProcessorRegistrationDelegate {
|
||||
|
||||
/**
|
||||
* Selectively invoke {@link MergedBeanDefinitionPostProcessor} instances
|
||||
* registered in the specified bean factory, resolving bean definitions as
|
||||
* well as any inner bean definitions that they may contain.
|
||||
* registered in the specified bean factory, resolving bean definitions and
|
||||
* any attributes if necessary as well as any inner bean definitions that
|
||||
* they may contain.
|
||||
* @param beanFactory the bean factory to use
|
||||
*/
|
||||
static void invokeMergedBeanDefinitionPostProcessors(DefaultListableBeanFactory beanFactory) {
|
||||
@@ -483,6 +485,9 @@ final class PostProcessorRegistrationDelegate {
|
||||
resolveInnerBeanDefinition(valueResolver, innerBd, (innerBeanName, innerBeanDefinition)
|
||||
-> postProcessRootBeanDefinition(postProcessors, innerBeanName, innerBeanType, innerBeanDefinition));
|
||||
}
|
||||
if (value instanceof TypedStringValue typedStringValue) {
|
||||
resolveTypeStringValue(typedStringValue);
|
||||
}
|
||||
}
|
||||
for (ValueHolder valueHolder : bd.getConstructorArgumentValues().getIndexedArgumentValues().values()) {
|
||||
Object value = valueHolder.getValue();
|
||||
@@ -491,6 +496,9 @@ final class PostProcessorRegistrationDelegate {
|
||||
resolveInnerBeanDefinition(valueResolver, innerBd, (innerBeanName, innerBeanDefinition)
|
||||
-> postProcessRootBeanDefinition(postProcessors, innerBeanName, innerBeanType, innerBeanDefinition));
|
||||
}
|
||||
if (value instanceof TypedStringValue typedStringValue) {
|
||||
resolveTypeStringValue(typedStringValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,6 +511,15 @@ final class PostProcessorRegistrationDelegate {
|
||||
});
|
||||
}
|
||||
|
||||
private void resolveTypeStringValue(TypedStringValue typedStringValue) {
|
||||
try {
|
||||
typedStringValue.resolveTargetType(this.beanFactory.getBeanClassLoader());
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> resolveBeanType(AbstractBeanDefinition bd) {
|
||||
if (!bd.hasBeanClass()) {
|
||||
try {
|
||||
|
||||
@@ -52,6 +52,8 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.testfixture.beans.Employee;
|
||||
import org.springframework.beans.testfixture.beans.Pet;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy.Implementation;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy.One;
|
||||
@@ -62,6 +64,7 @@ import org.springframework.context.annotation.AnnotationConfigUtils;
|
||||
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
|
||||
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.context.support.GenericXmlApplicationContext;
|
||||
import org.springframework.context.testfixture.context.annotation.AutowiredComponent;
|
||||
import org.springframework.context.testfixture.context.annotation.AutowiredGenericTemplate;
|
||||
import org.springframework.context.testfixture.context.annotation.CglibConfiguration;
|
||||
@@ -79,6 +82,7 @@ import org.springframework.context.testfixture.context.generator.SimpleComponent
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
|
||||
import org.springframework.core.test.tools.Compiled;
|
||||
@@ -441,6 +445,59 @@ class ApplicationContextAotGeneratorTests {
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class XmlSupport {
|
||||
|
||||
@Test
|
||||
void processAheadOfTimeWhenHasTypedStringValue() {
|
||||
GenericXmlApplicationContext applicationContext = new GenericXmlApplicationContext();
|
||||
applicationContext
|
||||
.load(new ClassPathResource("applicationContextAotGeneratorTests-values.xml", getClass()));
|
||||
testCompiledResult(applicationContext, (initializer, compiled) -> {
|
||||
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
|
||||
Employee employee = freshApplicationContext.getBean(Employee.class);
|
||||
assertThat(employee.getName()).isEqualTo("John Smith");
|
||||
assertThat(employee.getAge()).isEqualTo(42);
|
||||
assertThat(employee.getCompany()).isEqualTo("Acme Widgets, Inc.");
|
||||
assertThat(freshApplicationContext.getBean("pet", Pet.class)
|
||||
.getName()).isEqualTo("Fido");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void processAheadOfTimeWhenHasTypedStringValueWithType() {
|
||||
GenericXmlApplicationContext applicationContext = new GenericXmlApplicationContext();
|
||||
applicationContext
|
||||
.load(new ClassPathResource("applicationContextAotGeneratorTests-values-types.xml", getClass()));
|
||||
testCompiledResult(applicationContext, (initializer, compiled) -> {
|
||||
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
|
||||
Employee employee = freshApplicationContext.getBean(Employee.class);
|
||||
assertThat(employee.getName()).isEqualTo("John Smith");
|
||||
assertThat(employee.getAge()).isEqualTo(42);
|
||||
assertThat(employee.getCompany()).isEqualTo("Acme Widgets, Inc.");
|
||||
assertThat(compiled.getSourceFile(".*Employee__BeanDefinitions"))
|
||||
.contains("new TypedStringValue(\"42\", Integer.class");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void processAheadOfTimeWhenHasTypedStringValueWithExpression() {
|
||||
GenericXmlApplicationContext applicationContext = new GenericXmlApplicationContext();
|
||||
applicationContext
|
||||
.load(new ClassPathResource("applicationContextAotGeneratorTests-values-expressions.xml", getClass()));
|
||||
testCompiledResult(applicationContext, (initializer, compiled) -> {
|
||||
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
|
||||
Employee employee = freshApplicationContext.getBean(Employee.class);
|
||||
assertThat(employee.getName()).isEqualTo("John Smith");
|
||||
assertThat(employee.getAge()).isEqualTo(42);
|
||||
assertThat(employee.getCompany()).isEqualTo("Acme Widgets, Inc.");
|
||||
assertThat(freshApplicationContext.getBean("pet", Pet.class)
|
||||
.getName()).isEqualTo("Fido");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Consumer<List<? extends JdkProxyHint>> doesNotHaveProxyFor(Class<?> target) {
|
||||
return hints -> assertThat(hints).noneMatch(hint ->
|
||||
hint.getProxiedInterfaces().get(0).equals(TypeReference.of(target)));
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.beans.factory.config.AbstractFactoryBean;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.TypedStringValue;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
@@ -362,6 +363,34 @@ class GenericApplicationContextTests {
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshForAotLoadsTypedStringValueClassNameInProperty() {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition("java.lang.Integer");
|
||||
beanDefinition.getPropertyValues().add("value", new TypedStringValue("42", "java.lang.Integer"));
|
||||
context.registerBeanDefinition("number", beanDefinition);
|
||||
context.refreshForAotProcessing(new RuntimeHints());
|
||||
assertThat(getBeanDefinition(context, "number").getPropertyValues().get("value"))
|
||||
.isInstanceOfSatisfying(TypedStringValue.class, typeStringValue ->
|
||||
assertThat(typeStringValue.getTargetType()).isEqualTo(Integer.class));
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshForAotLoadsTypedStringValueClassNameInConstructorArgument() {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition("java.lang.Integer");
|
||||
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
|
||||
new TypedStringValue("42", "java.lang.Integer"));
|
||||
context.registerBeanDefinition("number", beanDefinition);
|
||||
context.refreshForAotProcessing(new RuntimeHints());
|
||||
assertThat(getBeanDefinition(context, "number").getConstructorArgumentValues()
|
||||
.getIndexedArgumentValue(0, TypedStringValue.class).getValue())
|
||||
.isInstanceOfSatisfying(TypedStringValue.class, typeStringValue ->
|
||||
assertThat(typeStringValue.getTargetType()).isEqualTo(Integer.class));
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshForAotInvokesBeanFactoryPostProcessors() {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?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="properties"
|
||||
class="org.springframework.beans.factory.config.PropertiesFactoryBean">
|
||||
<property name="properties">
|
||||
<props>
|
||||
<prop key="name">John Smith</prop>
|
||||
<prop key="age">42</prop>
|
||||
<prop key="company">Acme Widgets, Inc.</prop>
|
||||
</props>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="employee" class="org.springframework.beans.testfixture.beans.Employee">
|
||||
<property name="name" value="#{properties['name']}" />
|
||||
<property name="age" value="#{properties['age']}" />
|
||||
<property name="company" value="#{properties['company']}" />
|
||||
</bean>
|
||||
|
||||
<bean id="pet" class="org.springframework.beans.testfixture.beans.Pet">
|
||||
<constructor-arg index="0" value="Fido" />
|
||||
</bean>
|
||||
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?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="employee" class="org.springframework.beans.testfixture.beans.Employee">
|
||||
<property name="name" value="John Smith" />
|
||||
<property name="age">
|
||||
<value type="java.lang.Integer">42</value>
|
||||
</property>
|
||||
<property name="company" value="Acme Widgets, Inc." />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?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="employee" class="org.springframework.beans.testfixture.beans.Employee">
|
||||
<property name="name" value="John Smith" />
|
||||
<property name="age" value="42" />
|
||||
<property name="company" value="Acme Widgets, Inc." />
|
||||
</bean>
|
||||
|
||||
<bean id="pet" class="org.springframework.beans.testfixture.beans.Pet">
|
||||
<constructor-arg index="0" value="Fido" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
Reference in New Issue
Block a user