diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java index e12e4f3d2e..c4b9710280 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java @@ -97,15 +97,17 @@ class BeanDefinitionPropertiesCodeGenerator { BeanDefinitionPropertiesCodeGenerator(RuntimeHints hints, Predicate attributeFilter, GeneratedMethods generatedMethods, + List additionalDelegates, BiFunction customValueCodeGenerator) { this.hints = hints; this.attributeFilter = attributeFilter; - this.valueCodeGenerator = ValueCodeGenerator - .with(new ValueCodeGeneratorDelegateAdapter(customValueCodeGenerator)) - .add(BeanDefinitionPropertyValueCodeGeneratorDelegates.INSTANCES) - .add(ValueCodeGeneratorDelegates.INSTANCES) - .scoped(generatedMethods); + List 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 customValueCodeGenerator; - - ValueCodeGeneratorDelegateAdapter(BiFunction 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> threadLocal = ThreadLocal.withInitial(ArrayDeque::new); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java index 797808f463..0ef9210228 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java @@ -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 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 additionalDelegates = loader.load(Delegate.class).asList(); + return new BeanDefinitionPropertiesCodeGenerator(generationContext.getRuntimeHints(), + attributeFilter, beanRegistrationCode.getMethods(), + additionalDelegates, (name, value) -> generateValueCode(generationContext, name, value) + ).generateCode(beanDefinition); } @Nullable diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java index 9f9989b747..991578a2fc 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java @@ -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( diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java index 1e473202a7..17e985f135 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java @@ -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 attributeFilter, BiConsumer result) { + private void compile(Predicate attributeFilter, + BiConsumer result) { + compile(attributeFilter, Collections.emptyList(), result); + } + + private void compile(Predicate attributeFilter, List additionalDelegates, + BiConsumer 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); diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/aot/CustomBean.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/aot/CustomBean.java new file mode 100644 index 0000000000..8bd379c95a --- /dev/null +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/aot/CustomBean.java @@ -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; + } + +} diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/aot/CustomPropertyValue.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/aot/CustomPropertyValue.java new file mode 100644 index 0000000000..f73b51bc97 --- /dev/null +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/aot/CustomPropertyValue.java @@ -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; + } + } + +} diff --git a/spring-beans/src/testFixtures/resources/META-INF/spring/aot.factories b/spring-beans/src/testFixtures/resources/META-INF/spring/aot.factories new file mode 100644 index 0000000000..5f3eadf96d --- /dev/null +++ b/spring-beans/src/testFixtures/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.generate.ValueCodeGenerator$Delegate=\ +org.springframework.beans.testfixture.beans.factory.aot.CustomPropertyValue$ValueCodeGeneratorDelegate \ No newline at end of file