diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java index d00b6daeecf..c7bf287643e 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java @@ -40,7 +40,8 @@ import org.springframework.util.CollectionUtils; class ConfigurationPropertiesBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { @Override - public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + public ConfigurationPropertiesReflectionHintsContribution processAheadOfTime( + ConfigurableListableBeanFactory beanFactory) { String[] beanNames = beanFactory.getBeanNamesForAnnotation(ConfigurationProperties.class); List> types = new ArrayList<>(); for (String beanName : beanNames) { @@ -55,7 +56,7 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessor implements Be return null; } - private static final class ConfigurationPropertiesReflectionHintsContribution + static final class ConfigurationPropertiesReflectionHintsContribution implements BeanFactoryInitializationAotContribution { private final Iterable> types; @@ -70,6 +71,10 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessor implements Be BindableRuntimeHintsRegistrar.forTypes(this.types).registerHints(generationContext.getRuntimeHints()); } + Iterable> getTypes() { + return this.types; + } + } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java index 6ef68b8bd62..1b45fc8e5d2 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java @@ -16,35 +16,21 @@ package org.springframework.boot.context.properties; -import java.lang.reflect.Constructor; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.springframework.aot.generate.GenerationContext; -import org.springframework.aot.hint.ExecutableHint; -import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.TypeHint; import org.springframework.aot.hint.TypeReference; -import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.aot.test.generate.TestGenerationContext; import org.springframework.beans.factory.aot.AotServices; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.boot.context.properties.bind.ConstructorBinding; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.Environment; +import org.springframework.boot.context.properties.ConfigurationPropertiesBeanFactoryInitializationAotProcessor.ConfigurationPropertiesReflectionHintsContribution; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -68,209 +54,34 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests { @Test void processNoMatchesReturnsNullContribution() { - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - beanFactory.registerBeanDefinition("test", new RootBeanDefinition(String.class)); - assertThat(this.processor.processAheadOfTime(beanFactory)).isNull(); + assertThat(process(String.class)).isNull(); } @Test void processManuallyRegisteredSingleton() { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); beanFactory.registerSingleton("test", new SampleProperties()); - RuntimeHints runtimeHints = process(beanFactory); - assertThat(runtimeHints.reflection().getTypeHint(SampleProperties.class)) - .satisfies(javaBeanBinding(SampleProperties.class)); + ConfigurationPropertiesReflectionHintsContribution contribution = process(beanFactory); + assertThat(contribution.getTypes()).containsExactly(SampleProperties.class); + assertThat(typeHints(contribution).map(TypeHint::getType)) + .containsExactly(TypeReference.of(SampleProperties.class)); } @Test - void processJavaBeanConfigurationProperties() { - RuntimeHints runtimeHints = process(SampleProperties.class); - assertThat(runtimeHints.reflection().getTypeHint(SampleProperties.class)) - .satisfies(javaBeanBinding(SampleProperties.class)); + void processDefinedBean() { + ConfigurationPropertiesReflectionHintsContribution contribution = process(SampleProperties.class); + assertThat(contribution.getTypes()).containsExactly(SampleProperties.class); + assertThat(typeHints(contribution).map(TypeHint::getType)) + .containsExactly(TypeReference.of(SampleProperties.class)); } - @Test - void processJavaBeanConfigurationPropertiesWithSeveralConstructors() throws NoSuchMethodException { - RuntimeHints runtimeHints = process(SamplePropertiesWithSeveralConstructors.class); - assertThat(runtimeHints.reflection().getTypeHint(SamplePropertiesWithSeveralConstructors.class)) - .satisfies(javaBeanBinding(SamplePropertiesWithSeveralConstructors.class, - SamplePropertiesWithSeveralConstructors.class.getDeclaredConstructor())); - } - - @Test - void processJavaBeanConfigurationPropertiesWithMapOfPojo() { - RuntimeHints runtimeHints = process(SamplePropertiesWithMap.class); - List typeHints = runtimeHints.reflection().typeHints().toList(); - assertThat(typeHints).anySatisfy(javaBeanBinding(SamplePropertiesWithMap.class)); - assertThat(typeHints).anySatisfy(javaBeanBinding(Address.class)); - assertThat(typeHints).hasSize(2); - } - - @Test - void processJavaBeanConfigurationPropertiesWithListOfPojo() { - RuntimeHints runtimeHints = process(SamplePropertiesWithList.class); - List typeHints = runtimeHints.reflection().typeHints().toList(); - assertThat(typeHints).anySatisfy(javaBeanBinding(SamplePropertiesWithList.class)); - assertThat(typeHints).anySatisfy(javaBeanBinding(Address.class)); - assertThat(typeHints).hasSize(2); - } - - @Test - void processJavaBeanConfigurationPropertiesWitArrayOfPojo() { - RuntimeHints runtimeHints = process(SamplePropertiesWithArray.class); - List typeHints = runtimeHints.reflection().typeHints().toList(); - assertThat(typeHints).anySatisfy(javaBeanBinding(SamplePropertiesWithArray.class)); - assertThat(typeHints).anySatisfy(javaBeanBinding(Address.class)); - assertThat(typeHints).hasSize(2); - } - - @Test - void processJavaBeanConfigurationPropertiesWithListOfJavaType() { - RuntimeHints runtimeHints = process(SamplePropertiesWithSimpleList.class); - List typeHints = runtimeHints.reflection().typeHints().toList(); - assertThat(typeHints).anySatisfy(javaBeanBinding(SamplePropertiesWithSimpleList.class)); - assertThat(typeHints).hasSize(1); - } - - @Test - void processValueObjectConfigurationProperties() { - RuntimeHints runtimeHints = process(SampleImmutableProperties.class); - List typeHints = runtimeHints.reflection().typeHints().toList(); - assertThat(typeHints).anySatisfy(valueObjectBinding(SampleImmutableProperties.class, - SampleImmutableProperties.class.getDeclaredConstructors()[0])); - assertThat(typeHints).hasSize(1); - } - - @Test - void processValueObjectConfigurationPropertiesWithSpecificConstructor() throws NoSuchMethodException { - RuntimeHints runtimeHints = process(SampleImmutablePropertiesWithSeveralConstructors.class); - List typeHints = runtimeHints.reflection().typeHints().toList(); - assertThat(typeHints).anySatisfy(valueObjectBinding(SampleImmutablePropertiesWithSeveralConstructors.class, - SampleImmutablePropertiesWithSeveralConstructors.class.getDeclaredConstructor(String.class))); - assertThat(typeHints).hasSize(1); - } - - @Test - void processValueObjectConfigurationPropertiesWithSeveralLayersOfPojo() { - RuntimeHints runtimeHints = process(SampleImmutablePropertiesWithList.class); - List typeHints = runtimeHints.reflection().typeHints().toList(); - assertThat(typeHints).anySatisfy(valueObjectBinding(SampleImmutablePropertiesWithList.class, - SampleImmutablePropertiesWithList.class.getDeclaredConstructors()[0])); - assertThat(typeHints).anySatisfy(valueObjectBinding(Person.class, Person.class.getDeclaredConstructors()[0])); - assertThat(typeHints).anySatisfy(valueObjectBinding(Address.class, Address.class.getDeclaredConstructors()[0])); - assertThat(typeHints).hasSize(3); - } - - @Test - void processConfigurationPropertiesWithNestedTypeNotUsedIsIgnored() { - RuntimeHints runtimeHints = process(SamplePropertiesWithNested.class); - assertThat(runtimeHints.reflection().getTypeHint(SamplePropertiesWithNested.class)) - .satisfies(javaBeanBinding(SamplePropertiesWithNested.class)); - } - - @Test - void processConfigurationPropertiesWithNestedExternalType() { - RuntimeHints runtimeHints = process(SamplePropertiesWithExternalNested.class); - assertThat(runtimeHints.reflection().typeHints()) - .anySatisfy(javaBeanBinding(SamplePropertiesWithExternalNested.class)) - .anySatisfy(javaBeanBinding(SampleType.class)) - .anySatisfy(javaBeanBinding(SampleType.Nested.class)) - .hasSize(3); - } - - @Test - void processConfigurationPropertiesWithRecursiveType() { - RuntimeHints runtimeHints = process(SamplePropertiesWithRecursive.class); - assertThat(runtimeHints.reflection().typeHints()) - .anySatisfy(javaBeanBinding(SamplePropertiesWithRecursive.class)) - .anySatisfy(javaBeanBinding(Recursive.class)) - .hasSize(2); - } - - @Test - void processValueObjectConfigurationPropertiesWithRecursiveType() { - RuntimeHints runtimeHints = process(SampleImmutablePropertiesWithRecursive.class); - assertThat(runtimeHints.reflection().typeHints()) - .anySatisfy(valueObjectBinding(SampleImmutablePropertiesWithRecursive.class, - SampleImmutablePropertiesWithRecursive.class.getDeclaredConstructors()[0])) - .anySatisfy( - valueObjectBinding(ImmutableRecursive.class, ImmutableRecursive.class.getDeclaredConstructors()[0])) - .hasSize(2); - } - - @Test - void processConfigurationPropertiesWithWellKnownTypes() { - RuntimeHints runtimeHints = process(SamplePropertiesWithWellKnownTypes.class); - assertThat(runtimeHints.reflection().typeHints()) - .anySatisfy(javaBeanBinding(SamplePropertiesWithWellKnownTypes.class)) - .hasSize(1); - } - - @Test - void processConfigurationPropertiesWithCrossReference() { - RuntimeHints runtimeHints = process(SamplePropertiesWithCrossReference.class); - assertThat(runtimeHints.reflection().typeHints()) - .anySatisfy(javaBeanBinding(SamplePropertiesWithCrossReference.class)) - .anySatisfy(javaBeanBinding(CrossReferenceA.class)) - .anySatisfy(javaBeanBinding(CrossReferenceB.class)) - .hasSize(3); - } - - @Test - void processConfigurationPropertiesWithUnresolvedGeneric() { - RuntimeHints runtimeHints = process(SamplePropertiesWithGeneric.class); - assertThat(runtimeHints.reflection().typeHints()).anySatisfy(javaBeanBinding(SamplePropertiesWithGeneric.class)) - .anySatisfy(javaBeanBinding(GenericObject.class)); - } - - @Test - void processConfigurationPropertiesWithNestedGenerics() { - RuntimeHints runtimeHints = process(NestedGenerics.class); - assertThat(RuntimeHintsPredicates.reflection().onType(NestedGenerics.class)).accepts(runtimeHints); - assertThat(RuntimeHintsPredicates.reflection().onType(NestedGenerics.Nested.class)).accepts(runtimeHints); - } - - @Test - void processConfigurationPropertiesWithMultipleNestedClasses() { - RuntimeHints runtimeHints = process(TripleNested.class); - assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.class)).accepts(runtimeHints); - assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.DoubleNested.class)).accepts(runtimeHints); - assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.DoubleNested.Nested.class)) - .accepts(runtimeHints); - } - - private Consumer javaBeanBinding(Class type) { - return javaBeanBinding(type, type.getDeclaredConstructors()[0]); - } - - private Consumer javaBeanBinding(Class type, Constructor constructor) { - return (entry) -> { - assertThat(entry.getType()).isEqualTo(TypeReference.of(type)); - assertThat(entry.constructors()).singleElement().satisfies(match(constructor)); - assertThat(entry.getMemberCategories()).isEmpty(); - assertThat(entry.methods()).allMatch((t) -> t.getName().startsWith("set") || t.getName().startsWith("get") - || t.getName().startsWith("is")); - }; - } - - private Consumer valueObjectBinding(Class type, Constructor constructor) { - return (entry) -> { - assertThat(entry.getType()).isEqualTo(TypeReference.of(type)); - assertThat(entry.constructors()).singleElement().satisfies(match(constructor)); - assertThat(entry.getMemberCategories()).isEmpty(); - assertThat(entry.methods()).isEmpty(); - }; - } - - private Consumer match(Constructor constructor) { - return (executableHint) -> { - assertThat(executableHint.getName()).isEqualTo(""); - assertThat(Arrays.stream(constructor.getParameterTypes()).map(TypeReference::of).toList()) - .isEqualTo(executableHint.getParameterTypes()); - }; + Stream typeHints(ConfigurationPropertiesReflectionHintsContribution contribution) { + GenerationContext generationContext = new TestGenerationContext(); + contribution.applyTo(generationContext, mock(BeanFactoryInitializationCode.class)); + return generationContext.getRuntimeHints().reflection().typeHints(); } - private RuntimeHints process(Class... types) { + private ConfigurationPropertiesReflectionHintsContribution process(Class... types) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); for (Class type : types) { beanFactory.registerBeanDefinition(type.getName(), new RootBeanDefinition(type)); @@ -278,12 +89,8 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests { return process(beanFactory); } - private RuntimeHints process(ConfigurableListableBeanFactory beanFactory) { - BeanFactoryInitializationAotContribution contribution = this.processor.processAheadOfTime(beanFactory); - assertThat(contribution).isNotNull(); - GenerationContext generationContext = new TestGenerationContext(); - contribution.applyTo(generationContext, mock(BeanFactoryInitializationCode.class)); - return generationContext.getRuntimeHints(); + private ConfigurationPropertiesReflectionHintsContribution process(ConfigurableListableBeanFactory beanFactory) { + return this.processor.processAheadOfTime(beanFactory); } @ConfigurationProperties("test") @@ -291,375 +98,4 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests { } - @ConfigurationProperties("test") - public static class SamplePropertiesWithSeveralConstructors { - - SamplePropertiesWithSeveralConstructors() { - } - - SamplePropertiesWithSeveralConstructors(String ignored) { - } - - } - - @ConfigurationProperties("test") - public static class SamplePropertiesWithMap { - - public Map getAddresses() { - return Collections.emptyMap(); - } - - } - - @ConfigurationProperties("test") - public static class SamplePropertiesWithList { - - public List
getAllAddresses() { - return Collections.emptyList(); - } - - } - - @ConfigurationProperties("test") - public static class SamplePropertiesWithSimpleList { - - public List getNames() { - return Collections.emptyList(); - } - - } - - @ConfigurationProperties("test") - public static class SamplePropertiesWithArray { - - public Address[] getAllAddresses() { - return new Address[0]; - } - - } - - @ConfigurationProperties - public static class SampleImmutableProperties { - - @SuppressWarnings("unused") - private final String name; - - SampleImmutableProperties(String name) { - this.name = name; - } - - } - - @ConfigurationProperties - public static class SampleImmutablePropertiesWithSeveralConstructors { - - @SuppressWarnings("unused") - private final String name; - - @ConstructorBinding - SampleImmutablePropertiesWithSeveralConstructors(String name) { - this.name = name; - } - - SampleImmutablePropertiesWithSeveralConstructors() { - this("test"); - } - - } - - @ConfigurationProperties - public static class SampleImmutablePropertiesWithList { - - @SuppressWarnings("unused") - private final List family; - - SampleImmutablePropertiesWithList(List family) { - this.family = family; - } - - } - - @ConfigurationProperties("nested") - public static class SamplePropertiesWithNested { - - static class OneLevelDown { - - } - - } - - @ConfigurationProperties("nested") - public static class SamplePropertiesWithExternalNested { - - private String name; - - @NestedConfigurationProperty - private SampleType sampleType; - - public String getName() { - return this.name; - } - - public void setName(String name) { - this.name = name; - } - - public SampleType getSampleType() { - return this.sampleType; - } - - public void setSampleType(SampleType sampleType) { - this.sampleType = sampleType; - } - - } - - @ConfigurationProperties("recursive") - public static class SamplePropertiesWithRecursive { - - @NestedConfigurationProperty - private Recursive recursive; - - public Recursive getRecursive() { - return this.recursive; - } - - public void setRecursive(Recursive recursive) { - this.recursive = recursive; - } - - } - - @ConfigurationProperties - public static class SampleImmutablePropertiesWithRecursive { - - @NestedConfigurationProperty - private final ImmutableRecursive recursive; - - SampleImmutablePropertiesWithRecursive(ImmutableRecursive recursive) { - this.recursive = recursive; - } - - } - - @ConfigurationProperties("wellKnownTypes") - public static class SamplePropertiesWithWellKnownTypes implements ApplicationContextAware, EnvironmentAware { - - private ApplicationContext applicationContext; - - private Environment environment; - - public ApplicationContext getApplicationContext() { - return this.applicationContext; - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) { - this.applicationContext = applicationContext; - } - - public Environment getEnvironment() { - return this.environment; - } - - @Override - public void setEnvironment(Environment environment) { - this.environment = environment; - } - - } - - public static class SampleType { - - private final Nested nested = new Nested(); - - public Nested getNested() { - return this.nested; - } - - static class Nested { - - } - - } - - public static class Address { - - } - - public static class Person { - - @SuppressWarnings("unused") - private final String firstName; - - @SuppressWarnings("unused") - private final String lastName; - - @NestedConfigurationProperty - private final Address address; - - Person(String firstName, String lastName, Address address) { - this.firstName = firstName; - this.lastName = lastName; - this.address = address; - } - - } - - public static class Recursive { - - private Recursive recursive; - - public Recursive getRecursive() { - return this.recursive; - } - - public void setRecursive(Recursive recursive) { - this.recursive = recursive; - } - - } - - public static class ImmutableRecursive { - - @SuppressWarnings("unused") - private final ImmutableRecursive recursive; - - ImmutableRecursive(ImmutableRecursive recursive) { - this.recursive = recursive; - } - - } - - @ConfigurationProperties("crossreference") - public static class SamplePropertiesWithCrossReference { - - @NestedConfigurationProperty - private CrossReferenceA crossReferenceA; - - public void setCrossReferenceA(CrossReferenceA crossReferenceA) { - this.crossReferenceA = crossReferenceA; - } - - public CrossReferenceA getCrossReferenceA() { - return this.crossReferenceA; - } - - } - - public static class CrossReferenceA { - - @NestedConfigurationProperty - private CrossReferenceB crossReferenceB; - - public void setCrossReferenceB(CrossReferenceB crossReferenceB) { - this.crossReferenceB = crossReferenceB; - } - - public CrossReferenceB getCrossReferenceB() { - return this.crossReferenceB; - } - - } - - public static class CrossReferenceB { - - private CrossReferenceA crossReferenceA; - - public void setCrossReferenceA(CrossReferenceA crossReferenceA) { - this.crossReferenceA = crossReferenceA; - } - - public CrossReferenceA getCrossReferenceA() { - return this.crossReferenceA; - } - - } - - @ConfigurationProperties(prefix = "generic") - public static class SamplePropertiesWithGeneric { - - @NestedConfigurationProperty - private GenericObject generic; - - public GenericObject getGeneric() { - return this.generic; - } - - } - - public static final class GenericObject { - - private final T value; - - GenericObject(T value) { - this.value = value; - } - - public T getValue() { - return this.value; - } - - } - - @ConfigurationProperties(prefix = "nested-generics") - public static class NestedGenerics { - - private final Map> nested = new HashMap<>(); - - public Map> getNested() { - return this.nested; - } - - public static class Nested { - - private String field; - - public String getField() { - return this.field; - } - - public void setField(String field) { - this.field = field; - } - - } - - } - - @ConfigurationProperties(prefix = "triple-nested") - public static class TripleNested { - - private final DoubleNested doubleNested = new DoubleNested(); - - public DoubleNested getDoubleNested() { - return this.doubleNested; - } - - public static class DoubleNested { - - private final Nested nested = new Nested(); - - public Nested getNested() { - return this.nested; - } - - public static class Nested { - - private String field; - - public String getField() { - return this.field; - } - - public void setField(String field) { - this.field = field; - } - - } - - } - - } - }