Skip to content

Commit 409cecf

Browse files
committed
Add custom code generation for bean definition property values
This commits allows ValueCodeGenerator$Delegate implementations to be loaded from `META-INF/spring/aot.factories` and considered for code generation of bean definition property values. This default behavior in DefaultBeanRegistrationCodeFragments can be customized as usual. Closes gh-31427
1 parent 3c2c9ca commit 409cecf

File tree

7 files changed

+138
-28
lines changed

7 files changed

+138
-28
lines changed

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

+7-20
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,17 @@ class BeanDefinitionPropertiesCodeGenerator {
9797

9898
BeanDefinitionPropertiesCodeGenerator(RuntimeHints hints,
9999
Predicate<String> attributeFilter, GeneratedMethods generatedMethods,
100+
List<Delegate> additionalDelegates,
100101
BiFunction<String, Object, CodeBlock> customValueCodeGenerator) {
101102

102103
this.hints = hints;
103104
this.attributeFilter = attributeFilter;
104-
this.valueCodeGenerator = ValueCodeGenerator
105-
.with(new ValueCodeGeneratorDelegateAdapter(customValueCodeGenerator))
106-
.add(BeanDefinitionPropertyValueCodeGeneratorDelegates.INSTANCES)
107-
.add(ValueCodeGeneratorDelegates.INSTANCES)
108-
.scoped(generatedMethods);
105+
List<Delegate> allDelegates = new ArrayList<>();
106+
allDelegates.add((valueCodeGenerator, value) -> customValueCodeGenerator.apply(PropertyNamesStack.peek(), value));
107+
allDelegates.addAll(additionalDelegates);
108+
allDelegates.addAll(BeanDefinitionPropertyValueCodeGeneratorDelegates.INSTANCES);
109+
allDelegates.addAll(ValueCodeGeneratorDelegates.INSTANCES);
110+
this.valueCodeGenerator = ValueCodeGenerator.with(allDelegates).scoped(generatedMethods);
109111
}
110112

111113

@@ -373,21 +375,6 @@ private CodeBlock castIfNecessary(boolean castNecessary, Class<?> castType, Code
373375
}
374376

375377

376-
static class ValueCodeGeneratorDelegateAdapter implements Delegate {
377-
378-
private final BiFunction<String, Object, CodeBlock> customValueCodeGenerator;
379-
380-
ValueCodeGeneratorDelegateAdapter(BiFunction<String, Object, CodeBlock> customValueCodeGenerator) {
381-
this.customValueCodeGenerator = customValueCodeGenerator;
382-
}
383-
384-
@Override
385-
public CodeBlock generateCode(ValueCodeGenerator valueCodeGenerator, Object value) {
386-
return this.customValueCodeGenerator.apply(PropertyNamesStack.peek(), value);
387-
}
388-
}
389-
390-
391378
static class PropertyNamesStack {
392379

393380
private static final ThreadLocal<ArrayDeque<String>> threadLocal = ThreadLocal.withInitial(ArrayDeque::new);

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

+8-6
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
import org.springframework.aot.generate.MethodReference;
2929
import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
3030
import org.springframework.aot.generate.ValueCodeGenerator;
31+
import org.springframework.aot.generate.ValueCodeGenerator.Delegate;
3132
import org.springframework.beans.factory.FactoryBean;
33+
import org.springframework.beans.factory.aot.AotServices.Loader;
3234
import org.springframework.beans.factory.config.BeanDefinition;
3335
import org.springframework.beans.factory.config.BeanDefinitionHolder;
3436
import org.springframework.beans.factory.support.InstanceSupplier;
@@ -171,12 +173,12 @@ public CodeBlock generateSetBeanDefinitionPropertiesCode(
171173
GenerationContext generationContext,
172174
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
173175
Predicate<String> attributeFilter) {
174-
175-
return new BeanDefinitionPropertiesCodeGenerator(
176-
generationContext.getRuntimeHints(), attributeFilter,
177-
beanRegistrationCode.getMethods(),
178-
(name, value) -> generateValueCode(generationContext, name, value))
179-
.generateCode(beanDefinition);
176+
Loader loader = AotServices.factories(this.registeredBean.getBeanFactory().getBeanClassLoader());
177+
List<Delegate> additionalDelegates = loader.load(Delegate.class).asList();
178+
return new BeanDefinitionPropertiesCodeGenerator(generationContext.getRuntimeHints(),
179+
attributeFilter, beanRegistrationCode.getMethods(),
180+
additionalDelegates, (name, value) -> generateValueCode(generationContext, name, value)
181+
).generateCode(beanDefinition);
180182
}
181183

182184
@Nullable

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

+19
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
import org.springframework.beans.testfixture.beans.AnnotatedBean;
4646
import org.springframework.beans.testfixture.beans.GenericBean;
4747
import org.springframework.beans.testfixture.beans.TestBean;
48+
import org.springframework.beans.testfixture.beans.factory.aot.CustomBean;
49+
import org.springframework.beans.testfixture.beans.factory.aot.CustomPropertyValue;
4850
import org.springframework.beans.testfixture.beans.factory.aot.InnerBeanConfiguration;
4951
import org.springframework.beans.testfixture.beans.factory.aot.MockBeanRegistrationsCode;
5052
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBean;
@@ -605,6 +607,23 @@ void generateBeanDefinitionMethodWhenHasInnerBeanConstructorValueGeneratesMethod
605607
});
606608
}
607609

610+
@Test
611+
void generateBeanDefinitionMethodWhenCustomPropertyValueUsesCustomDelegate() {
612+
RootBeanDefinition beanDefinition = new RootBeanDefinition(CustomBean.class);
613+
beanDefinition.getPropertyValues().addPropertyValue(
614+
"customPropertyValue", new CustomPropertyValue("test"));
615+
RegisteredBean bean = registerBean(beanDefinition);
616+
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
617+
this.methodGeneratorFactory, bean, "test",
618+
Collections.emptyList());
619+
MethodReference method = generator.generateBeanDefinitionMethod(
620+
this.generationContext, this.beanRegistrationsCode);
621+
compile(method, (actual, compiled) ->
622+
assertThat(actual.getPropertyValues().get("customPropertyValue"))
623+
.isInstanceOfSatisfying(CustomPropertyValue.class, customPropertyValue
624+
-> assertThat(customPropertyValue.value()).isEqualTo("test")));
625+
}
626+
608627
@Test
609628
void generateBeanDefinitionMethodWhenHasAotContributionsAppliesContributions() {
610629
RegisteredBean registeredBean = registerBean(

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

+26-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.time.temporal.ChronoUnit;
2020
import java.util.ArrayList;
21+
import java.util.Collections;
2122
import java.util.List;
2223
import java.util.Map;
2324
import java.util.Set;
@@ -33,6 +34,7 @@
3334
import org.reactivestreams.Publisher;
3435

3536
import org.springframework.aot.generate.GeneratedClass;
37+
import org.springframework.aot.generate.ValueCodeGenerator.Delegate;
3638
import org.springframework.aot.hint.MemberCategory;
3739
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
3840
import org.springframework.aot.test.generate.TestGenerationContext;
@@ -49,6 +51,7 @@
4951
import org.springframework.beans.factory.support.ManagedMap;
5052
import org.springframework.beans.factory.support.ManagedSet;
5153
import org.springframework.beans.factory.support.RootBeanDefinition;
54+
import org.springframework.beans.testfixture.beans.factory.aot.CustomPropertyValue;
5255
import org.springframework.beans.testfixture.beans.factory.aot.DeferredTypeBuilder;
5356
import org.springframework.core.test.tools.Compiled;
5457
import org.springframework.core.test.tools.TestCompiler;
@@ -364,6 +367,21 @@ void propertyValuesWhenValuesOnFactoryBeanClass() {
364367
assertHasDeclaredFieldsHint(PropertyValuesFactoryBean.class);
365368
}
366369

370+
@Test
371+
void propertyValuesWhenCustomValuesUsingDelegate() {
372+
this.beanDefinition.setTargetType(PropertyValuesBean.class);
373+
this.beanDefinition.getPropertyValues().add("test", new CustomPropertyValue("test"));
374+
this.beanDefinition.getPropertyValues().add("spring", new CustomPropertyValue("framework"));
375+
compile(value -> true, List.of(new CustomPropertyValue.ValueCodeGeneratorDelegate()), (actual, compiled) -> {
376+
assertThat(actual.getPropertyValues().get("test")).isInstanceOfSatisfying(CustomPropertyValue.class,
377+
customPropertyValue -> assertThat(customPropertyValue.value()).isEqualTo("test"));
378+
assertThat(actual.getPropertyValues().get("spring")).isInstanceOfSatisfying(CustomPropertyValue.class,
379+
customPropertyValue -> assertThat(customPropertyValue.value()).isEqualTo("framework"));
380+
});
381+
assertHasMethodInvokeHints(PropertyValuesBean.class, "setTest", "setSpring");
382+
assertHasDeclaredFieldsHint(PropertyValuesBean.class);
383+
}
384+
367385
@Test
368386
void attributesWhenAllFiltered() {
369387
this.beanDefinition.setAttribute("a", "A");
@@ -548,12 +566,18 @@ private void compile(BiConsumer<RootBeanDefinition, Compiled> result) {
548566
compile(attribute -> true, result);
549567
}
550568

551-
private void compile(Predicate<String> attributeFilter, BiConsumer<RootBeanDefinition, Compiled> result) {
569+
private void compile(Predicate<String> attributeFilter,
570+
BiConsumer<RootBeanDefinition, Compiled> result) {
571+
compile(attributeFilter, Collections.emptyList(), result);
572+
}
573+
574+
private void compile(Predicate<String> attributeFilter, List<Delegate> additionalDelegates,
575+
BiConsumer<RootBeanDefinition, Compiled> result) {
552576
DeferredTypeBuilder typeBuilder = new DeferredTypeBuilder();
553577
GeneratedClass generatedClass = this.generationContext.getGeneratedClasses().addForFeature("TestCode", typeBuilder);
554578
BeanDefinitionPropertiesCodeGenerator codeGenerator = new BeanDefinitionPropertiesCodeGenerator(
555579
this.generationContext.getRuntimeHints(), attributeFilter,
556-
generatedClass.getMethods(), (name, value) -> null);
580+
generatedClass.getMethods(), additionalDelegates, (name, value) -> null);
557581
CodeBlock generatedCode = codeGenerator.generateCode(this.beanDefinition);
558582
typeBuilder.set(type -> {
559583
type.addModifiers(Modifier.PUBLIC);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2002-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.beans.testfixture.beans.factory.aot;
18+
19+
/**
20+
* A bean that uses {@link CustomPropertyValue}.
21+
*
22+
* @author Stephane Nicoll
23+
*/
24+
public class CustomBean {
25+
26+
private CustomPropertyValue customPropertyValue;
27+
28+
public CustomPropertyValue getCustomPropertyValue() {
29+
return this.customPropertyValue;
30+
}
31+
32+
public void setCustomPropertyValue(CustomPropertyValue customPropertyValue) {
33+
this.customPropertyValue = customPropertyValue;
34+
}
35+
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2002-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.beans.testfixture.beans.factory.aot;
18+
19+
import org.springframework.aot.generate.ValueCodeGenerator;
20+
import org.springframework.aot.generate.ValueCodeGenerator.Delegate;
21+
import org.springframework.javapoet.CodeBlock;
22+
23+
/**
24+
* A custom value with its code generator {@link Delegate} implementation.
25+
*
26+
* @author Stephane Nicoll
27+
*/
28+
public record CustomPropertyValue(String value) {
29+
30+
public static class ValueCodeGeneratorDelegate implements Delegate {
31+
@Override
32+
public CodeBlock generateCode(ValueCodeGenerator valueCodeGenerator, Object value) {
33+
if (value instanceof CustomPropertyValue data) {
34+
return CodeBlock.of("new $T($S)", CustomPropertyValue.class, data.value);
35+
}
36+
return null;
37+
}
38+
}
39+
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
org.springframework.aot.generate.ValueCodeGenerator$Delegate=\
2+
org.springframework.beans.testfixture.beans.factory.aot.CustomPropertyValue$ValueCodeGeneratorDelegate

0 commit comments

Comments
 (0)