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
This commit is contained in:
@@ -97,15 +97,17 @@ class BeanDefinitionPropertiesCodeGenerator {
|
||||
|
||||
BeanDefinitionPropertiesCodeGenerator(RuntimeHints hints,
|
||||
Predicate<String> attributeFilter, GeneratedMethods generatedMethods,
|
||||
List<Delegate> additionalDelegates,
|
||||
BiFunction<String, Object, CodeBlock> customValueCodeGenerator) {
|
||||
|
||||
this.hints = hints;
|
||||
this.attributeFilter = attributeFilter;
|
||||
this.valueCodeGenerator = ValueCodeGenerator
|
||||
.with(new ValueCodeGeneratorDelegateAdapter(customValueCodeGenerator))
|
||||
.add(BeanDefinitionPropertyValueCodeGeneratorDelegates.INSTANCES)
|
||||
.add(ValueCodeGeneratorDelegates.INSTANCES)
|
||||
.scoped(generatedMethods);
|
||||
List<Delegate> allDelegates = new ArrayList<>();
|
||||
allDelegates.add((valueCodeGenerator, value) -> customValueCodeGenerator.apply(PropertyNamesStack.peek(), value));
|
||||
allDelegates.addAll(additionalDelegates);
|
||||
allDelegates.addAll(BeanDefinitionPropertyValueCodeGeneratorDelegates.INSTANCES);
|
||||
allDelegates.addAll(ValueCodeGeneratorDelegates.INSTANCES);
|
||||
this.valueCodeGenerator = ValueCodeGenerator.with(allDelegates).scoped(generatedMethods);
|
||||
}
|
||||
|
||||
|
||||
@@ -373,21 +375,6 @@ class BeanDefinitionPropertiesCodeGenerator {
|
||||
}
|
||||
|
||||
|
||||
static class ValueCodeGeneratorDelegateAdapter implements Delegate {
|
||||
|
||||
private final BiFunction<String, Object, CodeBlock> customValueCodeGenerator;
|
||||
|
||||
ValueCodeGeneratorDelegateAdapter(BiFunction<String, Object, CodeBlock> customValueCodeGenerator) {
|
||||
this.customValueCodeGenerator = customValueCodeGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock generateCode(ValueCodeGenerator valueCodeGenerator, Object value) {
|
||||
return this.customValueCodeGenerator.apply(PropertyNamesStack.peek(), value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class PropertyNamesStack {
|
||||
|
||||
private static final ThreadLocal<ArrayDeque<String>> threadLocal = ThreadLocal.withInitial(ArrayDeque::new);
|
||||
|
||||
@@ -28,7 +28,9 @@ import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.generate.MethodReference;
|
||||
import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
|
||||
import org.springframework.aot.generate.ValueCodeGenerator;
|
||||
import org.springframework.aot.generate.ValueCodeGenerator.Delegate;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.aot.AotServices.Loader;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.support.InstanceSupplier;
|
||||
@@ -171,12 +173,12 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
|
||||
GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
|
||||
Predicate<String> attributeFilter) {
|
||||
|
||||
return new BeanDefinitionPropertiesCodeGenerator(
|
||||
generationContext.getRuntimeHints(), attributeFilter,
|
||||
beanRegistrationCode.getMethods(),
|
||||
(name, value) -> generateValueCode(generationContext, name, value))
|
||||
.generateCode(beanDefinition);
|
||||
Loader loader = AotServices.factories(this.registeredBean.getBeanFactory().getBeanClassLoader());
|
||||
List<Delegate> additionalDelegates = loader.load(Delegate.class).asList();
|
||||
return new BeanDefinitionPropertiesCodeGenerator(generationContext.getRuntimeHints(),
|
||||
attributeFilter, beanRegistrationCode.getMethods(),
|
||||
additionalDelegates, (name, value) -> generateValueCode(generationContext, name, value)
|
||||
).generateCode(beanDefinition);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -45,6 +45,8 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.testfixture.beans.AnnotatedBean;
|
||||
import org.springframework.beans.testfixture.beans.GenericBean;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.CustomBean;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.CustomPropertyValue;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.InnerBeanConfiguration;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.MockBeanRegistrationsCode;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBean;
|
||||
@@ -605,6 +607,23 @@ class BeanDefinitionMethodGeneratorTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateBeanDefinitionMethodWhenCustomPropertyValueUsesCustomDelegate() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(CustomBean.class);
|
||||
beanDefinition.getPropertyValues().addPropertyValue(
|
||||
"customPropertyValue", new CustomPropertyValue("test"));
|
||||
RegisteredBean bean = registerBean(beanDefinition);
|
||||
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
|
||||
this.methodGeneratorFactory, bean, "test",
|
||||
Collections.emptyList());
|
||||
MethodReference method = generator.generateBeanDefinitionMethod(
|
||||
this.generationContext, this.beanRegistrationsCode);
|
||||
compile(method, (actual, compiled) ->
|
||||
assertThat(actual.getPropertyValues().get("customPropertyValue"))
|
||||
.isInstanceOfSatisfying(CustomPropertyValue.class, customPropertyValue
|
||||
-> assertThat(customPropertyValue.value()).isEqualTo("test")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateBeanDefinitionMethodWhenHasAotContributionsAppliesContributions() {
|
||||
RegisteredBean registeredBean = registerBean(
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.beans.factory.aot;
|
||||
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -33,6 +34,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.aot.generate.GeneratedClass;
|
||||
import org.springframework.aot.generate.ValueCodeGenerator.Delegate;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
@@ -49,6 +51,7 @@ import org.springframework.beans.factory.support.ManagedList;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.beans.factory.support.ManagedSet;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.CustomPropertyValue;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.DeferredTypeBuilder;
|
||||
import org.springframework.core.test.tools.Compiled;
|
||||
import org.springframework.core.test.tools.TestCompiler;
|
||||
@@ -364,6 +367,21 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
|
||||
assertHasDeclaredFieldsHint(PropertyValuesFactoryBean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertyValuesWhenCustomValuesUsingDelegate() {
|
||||
this.beanDefinition.setTargetType(PropertyValuesBean.class);
|
||||
this.beanDefinition.getPropertyValues().add("test", new CustomPropertyValue("test"));
|
||||
this.beanDefinition.getPropertyValues().add("spring", new CustomPropertyValue("framework"));
|
||||
compile(value -> true, List.of(new CustomPropertyValue.ValueCodeGeneratorDelegate()), (actual, compiled) -> {
|
||||
assertThat(actual.getPropertyValues().get("test")).isInstanceOfSatisfying(CustomPropertyValue.class,
|
||||
customPropertyValue -> assertThat(customPropertyValue.value()).isEqualTo("test"));
|
||||
assertThat(actual.getPropertyValues().get("spring")).isInstanceOfSatisfying(CustomPropertyValue.class,
|
||||
customPropertyValue -> assertThat(customPropertyValue.value()).isEqualTo("framework"));
|
||||
});
|
||||
assertHasMethodInvokeHints(PropertyValuesBean.class, "setTest", "setSpring");
|
||||
assertHasDeclaredFieldsHint(PropertyValuesBean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void attributesWhenAllFiltered() {
|
||||
this.beanDefinition.setAttribute("a", "A");
|
||||
@@ -548,12 +566,18 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
|
||||
compile(attribute -> true, result);
|
||||
}
|
||||
|
||||
private void compile(Predicate<String> attributeFilter, BiConsumer<RootBeanDefinition, Compiled> result) {
|
||||
private void compile(Predicate<String> attributeFilter,
|
||||
BiConsumer<RootBeanDefinition, Compiled> result) {
|
||||
compile(attributeFilter, Collections.emptyList(), result);
|
||||
}
|
||||
|
||||
private void compile(Predicate<String> attributeFilter, List<Delegate> additionalDelegates,
|
||||
BiConsumer<RootBeanDefinition, Compiled> result) {
|
||||
DeferredTypeBuilder typeBuilder = new DeferredTypeBuilder();
|
||||
GeneratedClass generatedClass = this.generationContext.getGeneratedClasses().addForFeature("TestCode", typeBuilder);
|
||||
BeanDefinitionPropertiesCodeGenerator codeGenerator = new BeanDefinitionPropertiesCodeGenerator(
|
||||
this.generationContext.getRuntimeHints(), attributeFilter,
|
||||
generatedClass.getMethods(), (name, value) -> null);
|
||||
generatedClass.getMethods(), additionalDelegates, (name, value) -> null);
|
||||
CodeBlock generatedCode = codeGenerator.generateCode(this.beanDefinition);
|
||||
typeBuilder.set(type -> {
|
||||
type.addModifiers(Modifier.PUBLIC);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.beans.testfixture.beans.factory.aot;
|
||||
|
||||
/**
|
||||
* A bean that uses {@link CustomPropertyValue}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class CustomBean {
|
||||
|
||||
private CustomPropertyValue customPropertyValue;
|
||||
|
||||
public CustomPropertyValue getCustomPropertyValue() {
|
||||
return this.customPropertyValue;
|
||||
}
|
||||
|
||||
public void setCustomPropertyValue(CustomPropertyValue customPropertyValue) {
|
||||
this.customPropertyValue = customPropertyValue;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.beans.testfixture.beans.factory.aot;
|
||||
|
||||
import org.springframework.aot.generate.ValueCodeGenerator;
|
||||
import org.springframework.aot.generate.ValueCodeGenerator.Delegate;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
|
||||
/**
|
||||
* A custom value with its code generator {@link Delegate} implementation.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public record CustomPropertyValue(String value) {
|
||||
|
||||
public static class ValueCodeGeneratorDelegate implements Delegate {
|
||||
@Override
|
||||
public CodeBlock generateCode(ValueCodeGenerator valueCodeGenerator, Object value) {
|
||||
if (value instanceof CustomPropertyValue data) {
|
||||
return CodeBlock.of("new $T($S)", CustomPropertyValue.class, data.value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.aot.generate.ValueCodeGenerator$Delegate=\
|
||||
org.springframework.beans.testfixture.beans.factory.aot.CustomPropertyValue$ValueCodeGeneratorDelegate
|
||||
Reference in New Issue
Block a user