Skip to content

Commit 37c2619

Browse files
committed
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
1 parent 15c1941 commit 37c2619

File tree

8 files changed

+197
-4
lines changed

8 files changed

+197
-4
lines changed

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.beans.factory.config.BeanDefinition;
3939
import org.springframework.beans.factory.config.BeanReference;
4040
import org.springframework.beans.factory.config.RuntimeBeanReference;
41+
import org.springframework.beans.factory.config.TypedStringValue;
4142
import org.springframework.beans.factory.support.ManagedList;
4243
import org.springframework.beans.factory.support.ManagedMap;
4344
import org.springframework.beans.factory.support.ManagedSet;
@@ -88,7 +89,8 @@ class BeanDefinitionPropertyValueCodeGenerator {
8889
new ListDelegate(),
8990
new SetDelegate(),
9091
new MapDelegate(),
91-
new BeanReferenceDelegate()
92+
new BeanReferenceDelegate(),
93+
new TypedStringValueDelegate()
9294
));
9395
}
9496

@@ -569,4 +571,31 @@ else if (value instanceof BeanReference beanReference) {
569571
}
570572
}
571573

574+
/**
575+
* {@link Delegate} for {@link TypedStringValue} types.
576+
*/
577+
private class TypedStringValueDelegate implements Delegate {
578+
579+
@Override
580+
public CodeBlock generateCode(Object value, ResolvableType type) {
581+
if (value instanceof TypedStringValue typedStringValue) {
582+
return generateTypeStringValueCode(typedStringValue);
583+
}
584+
return null;
585+
}
586+
587+
private CodeBlock generateTypeStringValueCode(TypedStringValue typedStringValue) {
588+
String value = typedStringValue.getValue();
589+
if (typedStringValue.hasTargetType()) {
590+
return CodeBlock.of("new $T($S, $L)", TypedStringValue.class, value,
591+
generateCode(typedStringValue.getTargetType()));
592+
}
593+
return generateCode(value);
594+
}
595+
596+
private CodeBlock generateCode(@Nullable Object value) {
597+
return BeanDefinitionPropertyValueCodeGenerator.this.generateCode(value);
598+
}
599+
}
600+
572601
}

spring-beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.beans.factory.config;
1818

19+
import java.util.Comparator;
20+
1921
import org.springframework.beans.BeanMetadataElement;
2022
import org.springframework.lang.Nullable;
2123
import org.springframework.util.Assert;
@@ -35,7 +37,7 @@
3537
* @see BeanDefinition#getPropertyValues
3638
* @see org.springframework.beans.MutablePropertyValues#addPropertyValue
3739
*/
38-
public class TypedStringValue implements BeanMetadataElement {
40+
public class TypedStringValue implements BeanMetadataElement, Comparable<TypedStringValue> {
3941

4042
@Nullable
4143
private String value;
@@ -213,6 +215,10 @@ public boolean isDynamic() {
213215
return this.dynamic;
214216
}
215217

218+
@Override
219+
public int compareTo(@Nullable TypedStringValue o) {
220+
return Comparator.comparing(TypedStringValue::getValue).compare(this, o);
221+
}
216222

217223
@Override
218224
public boolean equals(@Nullable Object other) {

spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.beans.factory.config.BeanPostProcessor;
3535
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
3636
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
37+
import org.springframework.beans.factory.config.TypedStringValue;
3738
import org.springframework.beans.factory.support.AbstractBeanDefinition;
3839
import org.springframework.beans.factory.support.AbstractBeanFactory;
3940
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@@ -311,8 +312,9 @@ static <T extends BeanPostProcessor> List<T> loadBeanPostProcessors(
311312

312313
/**
313314
* Selectively invoke {@link MergedBeanDefinitionPostProcessor} instances
314-
* registered in the specified bean factory, resolving bean definitions as
315-
* well as any inner bean definitions that they may contain.
315+
* registered in the specified bean factory, resolving bean definitions and
316+
* any attributes if necessary as well as any inner bean definitions that
317+
* they may contain.
316318
* @param beanFactory the bean factory to use
317319
*/
318320
static void invokeMergedBeanDefinitionPostProcessors(DefaultListableBeanFactory beanFactory) {
@@ -483,6 +485,9 @@ private void postProcessRootBeanDefinition(List<MergedBeanDefinitionPostProcesso
483485
resolveInnerBeanDefinition(valueResolver, innerBd, (innerBeanName, innerBeanDefinition)
484486
-> postProcessRootBeanDefinition(postProcessors, innerBeanName, innerBeanType, innerBeanDefinition));
485487
}
488+
if (value instanceof TypedStringValue typedStringValue) {
489+
resolveTypeStringValue(typedStringValue);
490+
}
486491
}
487492
for (ValueHolder valueHolder : bd.getConstructorArgumentValues().getIndexedArgumentValues().values()) {
488493
Object value = valueHolder.getValue();
@@ -491,6 +496,9 @@ private void postProcessRootBeanDefinition(List<MergedBeanDefinitionPostProcesso
491496
resolveInnerBeanDefinition(valueResolver, innerBd, (innerBeanName, innerBeanDefinition)
492497
-> postProcessRootBeanDefinition(postProcessors, innerBeanName, innerBeanType, innerBeanDefinition));
493498
}
499+
if (value instanceof TypedStringValue typedStringValue) {
500+
resolveTypeStringValue(typedStringValue);
501+
}
494502
}
495503
}
496504

@@ -503,6 +511,15 @@ private void resolveInnerBeanDefinition(BeanDefinitionValueResolver valueResolve
503511
});
504512
}
505513

514+
private void resolveTypeStringValue(TypedStringValue typedStringValue) {
515+
try {
516+
typedStringValue.resolveTargetType(this.beanFactory.getBeanClassLoader());
517+
}
518+
catch (ClassNotFoundException ex) {
519+
// ignore
520+
}
521+
}
522+
506523
private Class<?> resolveBeanType(AbstractBeanDefinition bd) {
507524
if (!bd.hasBeanClass()) {
508525
try {

spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
5353
import org.springframework.beans.factory.support.RegisteredBean;
5454
import org.springframework.beans.factory.support.RootBeanDefinition;
55+
import org.springframework.beans.testfixture.beans.Employee;
56+
import org.springframework.beans.testfixture.beans.Pet;
5557
import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy;
5658
import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy.Implementation;
5759
import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy.One;
@@ -62,6 +64,7 @@
6264
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
6365
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
6466
import org.springframework.context.support.GenericApplicationContext;
67+
import org.springframework.context.support.GenericXmlApplicationContext;
6568
import org.springframework.context.testfixture.context.annotation.AutowiredComponent;
6669
import org.springframework.context.testfixture.context.annotation.AutowiredGenericTemplate;
6770
import org.springframework.context.testfixture.context.annotation.CglibConfiguration;
@@ -79,6 +82,7 @@
7982
import org.springframework.core.env.ConfigurableEnvironment;
8083
import org.springframework.core.env.Environment;
8184
import org.springframework.core.env.PropertySource;
85+
import org.springframework.core.io.ClassPathResource;
8286
import org.springframework.core.io.ResourceLoader;
8387
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
8488
import org.springframework.core.test.tools.Compiled;
@@ -441,6 +445,59 @@ static Stream<Arguments> activeProfilesParameters() {
441445

442446
}
443447

448+
@Nested
449+
class XmlSupport {
450+
451+
@Test
452+
void processAheadOfTimeWhenHasTypedStringValue() {
453+
GenericXmlApplicationContext applicationContext = new GenericXmlApplicationContext();
454+
applicationContext
455+
.load(new ClassPathResource("applicationContextAotGeneratorTests-values.xml", getClass()));
456+
testCompiledResult(applicationContext, (initializer, compiled) -> {
457+
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
458+
Employee employee = freshApplicationContext.getBean(Employee.class);
459+
assertThat(employee.getName()).isEqualTo("John Smith");
460+
assertThat(employee.getAge()).isEqualTo(42);
461+
assertThat(employee.getCompany()).isEqualTo("Acme Widgets, Inc.");
462+
assertThat(freshApplicationContext.getBean("pet", Pet.class)
463+
.getName()).isEqualTo("Fido");
464+
});
465+
}
466+
467+
@Test
468+
void processAheadOfTimeWhenHasTypedStringValueWithType() {
469+
GenericXmlApplicationContext applicationContext = new GenericXmlApplicationContext();
470+
applicationContext
471+
.load(new ClassPathResource("applicationContextAotGeneratorTests-values-types.xml", getClass()));
472+
testCompiledResult(applicationContext, (initializer, compiled) -> {
473+
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
474+
Employee employee = freshApplicationContext.getBean(Employee.class);
475+
assertThat(employee.getName()).isEqualTo("John Smith");
476+
assertThat(employee.getAge()).isEqualTo(42);
477+
assertThat(employee.getCompany()).isEqualTo("Acme Widgets, Inc.");
478+
assertThat(compiled.getSourceFile(".*Employee__BeanDefinitions"))
479+
.contains("new TypedStringValue(\"42\", Integer.class");
480+
});
481+
}
482+
483+
@Test
484+
void processAheadOfTimeWhenHasTypedStringValueWithExpression() {
485+
GenericXmlApplicationContext applicationContext = new GenericXmlApplicationContext();
486+
applicationContext
487+
.load(new ClassPathResource("applicationContextAotGeneratorTests-values-expressions.xml", getClass()));
488+
testCompiledResult(applicationContext, (initializer, compiled) -> {
489+
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
490+
Employee employee = freshApplicationContext.getBean(Employee.class);
491+
assertThat(employee.getName()).isEqualTo("John Smith");
492+
assertThat(employee.getAge()).isEqualTo(42);
493+
assertThat(employee.getCompany()).isEqualTo("Acme Widgets, Inc.");
494+
assertThat(freshApplicationContext.getBean("pet", Pet.class)
495+
.getName()).isEqualTo("Fido");
496+
});
497+
}
498+
499+
}
500+
444501
private Consumer<List<? extends JdkProxyHint>> doesNotHaveProxyFor(Class<?> target) {
445502
return hints -> assertThat(hints).noneMatch(hint ->
446503
hint.getProxiedInterfaces().get(0).equals(TypeReference.of(target)));

spring-context/src/test/java/org/springframework/context/support/GenericApplicationContextTests.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.beans.factory.config.BeanDefinition;
3535
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
3636
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
37+
import org.springframework.beans.factory.config.TypedStringValue;
3738
import org.springframework.beans.factory.support.AbstractBeanDefinition;
3839
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
3940
import org.springframework.beans.factory.support.GenericBeanDefinition;
@@ -362,6 +363,34 @@ void refreshForAotLoadsBeanClassNameOfPropertyValueInnerBeanDefinition() {
362363
context.close();
363364
}
364365

366+
@Test
367+
void refreshForAotLoadsTypedStringValueClassNameInProperty() {
368+
GenericApplicationContext context = new GenericApplicationContext();
369+
RootBeanDefinition beanDefinition = new RootBeanDefinition("java.lang.Integer");
370+
beanDefinition.getPropertyValues().add("value", new TypedStringValue("42", "java.lang.Integer"));
371+
context.registerBeanDefinition("number", beanDefinition);
372+
context.refreshForAotProcessing(new RuntimeHints());
373+
assertThat(getBeanDefinition(context, "number").getPropertyValues().get("value"))
374+
.isInstanceOfSatisfying(TypedStringValue.class, typeStringValue ->
375+
assertThat(typeStringValue.getTargetType()).isEqualTo(Integer.class));
376+
context.close();
377+
}
378+
379+
@Test
380+
void refreshForAotLoadsTypedStringValueClassNameInConstructorArgument() {
381+
GenericApplicationContext context = new GenericApplicationContext();
382+
RootBeanDefinition beanDefinition = new RootBeanDefinition("java.lang.Integer");
383+
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
384+
new TypedStringValue("42", "java.lang.Integer"));
385+
context.registerBeanDefinition("number", beanDefinition);
386+
context.refreshForAotProcessing(new RuntimeHints());
387+
assertThat(getBeanDefinition(context, "number").getConstructorArgumentValues()
388+
.getIndexedArgumentValue(0, TypedStringValue.class).getValue())
389+
.isInstanceOfSatisfying(TypedStringValue.class, typeStringValue ->
390+
assertThat(typeStringValue.getTargetType()).isEqualTo(Integer.class));
391+
context.close();
392+
}
393+
365394
@Test
366395
void refreshForAotInvokesBeanFactoryPostProcessors() {
367396
GenericApplicationContext context = new GenericApplicationContext();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
4+
5+
<bean id="properties"
6+
class="org.springframework.beans.factory.config.PropertiesFactoryBean">
7+
<property name="properties">
8+
<props>
9+
<prop key="name">John Smith</prop>
10+
<prop key="age">42</prop>
11+
<prop key="company">Acme Widgets, Inc.</prop>
12+
</props>
13+
</property>
14+
</bean>
15+
16+
<bean id="employee" class="org.springframework.beans.testfixture.beans.Employee">
17+
<property name="name" value="#{properties['name']}" />
18+
<property name="age" value="#{properties['age']}" />
19+
<property name="company" value="#{properties['company']}" />
20+
</bean>
21+
22+
<bean id="pet" class="org.springframework.beans.testfixture.beans.Pet">
23+
<constructor-arg index="0" value="Fido" />
24+
</bean>
25+
26+
27+
</beans>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
4+
5+
<bean id="employee" class="org.springframework.beans.testfixture.beans.Employee">
6+
<property name="name" value="John Smith" />
7+
<property name="age">
8+
<value type="java.lang.Integer">42</value>
9+
</property>
10+
<property name="company" value="Acme Widgets, Inc." />
11+
</bean>
12+
13+
</beans>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
4+
5+
<bean id="employee" class="org.springframework.beans.testfixture.beans.Employee">
6+
<property name="name" value="John Smith" />
7+
<property name="age" value="42" />
8+
<property name="company" value="Acme Widgets, Inc." />
9+
</bean>
10+
11+
<bean id="pet" class="org.springframework.beans.testfixture.beans.Pet">
12+
<constructor-arg index="0" value="Fido" />
13+
</bean>
14+
15+
</beans>

0 commit comments

Comments
 (0)