From 785588826e3ccf09e73a9e534b3b7fd425ee0550 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Wed, 20 Jul 2022 16:39:15 +0200 Subject: [PATCH] Fix nested type discovery in ConfigurationPropertiesReflectionHintsProcessor The old implementation picked up nested types only if they have been annotated with NestedConfigurationProperty, which not all nested properties are. Now the processor takes into account all referenced types. It ignores only types in the java package and interfaces. This commit also moves some of the tests from ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests to ConfigurationPropertiesReflectionHintsProcessorTests for easier discoverability Closes gh-31708 --- ...ionPropertiesReflectionHintsProcessor.java | 75 +-- ...actoryInitializationAotProcessorTests.java | 293 ------------ ...opertiesReflectionHintsProcessorTests.java | 437 ++++++++++++++++++ 3 files changed, 458 insertions(+), 347 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessorTests.java diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessor.java index 797456c028..fae939d638 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessor.java @@ -21,7 +21,6 @@ import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Constructor; -import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; @@ -35,12 +34,10 @@ import org.springframework.beans.BeanInfoFactory; import org.springframework.beans.ExtendedBeanInfoFactory; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.core.ResolvableType; -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.util.ReflectionUtils; /** * Registers a given type on {@link ReflectionHints} for binding purposes, discovering any - * nested type it may expose via a property. + * referenced types it may expose via a property. * * @author Andy Wilkinson * @author Moritz Halbritter @@ -77,12 +74,22 @@ public final class ConfigurationPropertiesReflectionHintsProcessor { .process(reflectionHints); } - private void processNestedType(Class type, ReflectionHints reflectionHints) { - processNestedType(type, getBindConstructor(type, true), reflectionHints); + private void processType(Class type, ReflectionHints reflectionHints) { + if (isTypeIgnored(type)) { + return; + } + new ConfigurationPropertiesReflectionHintsProcessor(type, getBindConstructor(type, true), this.seen) + .process(reflectionHints); } - private void processNestedType(Class type, Constructor bindConstructor, ReflectionHints reflectionHints) { - new ConfigurationPropertiesReflectionHintsProcessor(type, bindConstructor, this.seen).process(reflectionHints); + private boolean isTypeIgnored(Class type) { + if (type.getPackageName().startsWith("java.")) { + return true; + } + if (type.isInterface()) { + return true; + } + return false; } private static Constructor getBindConstructor(Class type, boolean nestedType) { @@ -118,9 +125,8 @@ public final class ConfigurationPropertiesReflectionHintsProcessor { private void handleValueObjectProperties(ReflectionHints reflectionHints) { for (int i = 0; i < this.bindConstructor.getParameterCount(); i++) { - String propertyName = this.bindConstructor.getParameters()[i].getName(); ResolvableType propertyType = ResolvableType.forConstructorParameter(this.bindConstructor, i); - handleProperty(reflectionHints, propertyName, propertyType); + registerType(reflectionHints, propertyType); } } @@ -129,16 +135,12 @@ public final class ConfigurationPropertiesReflectionHintsProcessor { Method readMethod = propertyDescriptor.getReadMethod(); if (readMethod != null) { ResolvableType propertyType = ResolvableType.forMethodReturnType(readMethod, this.type); - String propertyName = propertyDescriptor.getName(); - if (isSetterMandatory(propertyName, propertyType) && propertyDescriptor.getWriteMethod() == null) { - continue; - } - handleProperty(reflectionHints, propertyName, propertyType); + registerType(reflectionHints, propertyType); } } } - private void handleProperty(ReflectionHints reflectionHints, String propertyName, ResolvableType propertyType) { + private void registerType(ReflectionHints reflectionHints, ResolvableType propertyType) { Class propertyClass = propertyType.resolve(); if (propertyClass == null) { return; @@ -148,27 +150,13 @@ public final class ConfigurationPropertiesReflectionHintsProcessor { } Class componentType = getComponentType(propertyType); if (componentType != null) { - // Can be a list of simple types - if (!isJavaType(componentType)) { - processNestedType(componentType, reflectionHints); - } + processType(componentType, reflectionHints); } - else if (isNestedType(propertyName, propertyClass)) { - processNestedType(propertyClass, reflectionHints); + else { + processType(propertyClass, reflectionHints); } } - private boolean isSetterMandatory(String propertyName, ResolvableType propertyType) { - Class propertyClass = propertyType.resolve(); - if (propertyClass == null) { - return true; - } - if (getComponentType(propertyType) != null) { - return false; - } - return !isNestedType(propertyName, propertyClass); - } - private Class getComponentType(ResolvableType propertyType) { Class propertyClass = propertyType.toClass(); if (propertyType.isArray()) { @@ -183,27 +171,6 @@ public final class ConfigurationPropertiesReflectionHintsProcessor { return null; } - /** - * Specify whether the specified property refer to a nested type. A nested type - * represents a sub-namespace that need to be fully resolved. - * @param propertyName the name of the property - * @param propertyType the type of the property - * @return whether the specified {@code propertyType} is a nested type - */ - private boolean isNestedType(String propertyName, Class propertyType) { - if (this.type.equals(propertyType.getDeclaringClass())) { - return true; - } - else { - Field field = ReflectionUtils.findField(this.type, propertyName); - return field != null && MergedAnnotations.from(field).isPresent(NestedConfigurationProperty.class); - } - } - - private boolean isJavaType(Class candidate) { - return candidate.getPackageName().startsWith("java."); - } - private static BeanInfo getBeanInfo(Class beanType) { try { BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanType); 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 5bbabde9ec..cb1bb4ce15 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 @@ -18,9 +18,7 @@ package org.springframework.boot.context.properties; import java.lang.reflect.Constructor; import java.util.Arrays; -import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.function.Consumer; import org.junit.jupiter.api.Test; @@ -41,10 +39,6 @@ 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.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.Environment; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -88,65 +82,6 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests { (hint) -> assertThat(hint.getMemberCategories()).contains(MemberCategory.INVOKE_DECLARED_METHODS)); } - @Test - void processJavaBeanConfigurationProperties() { - RuntimeHints runtimeHints = process(SampleProperties.class); - assertThat(runtimeHints.reflection().getTypeHint(SampleProperties.class)) - .satisfies(javaBeanBinding(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(3); - } - - @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(3); - } - - @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(3); - } - - @Test - void processJavaBeanConfigurationPropertiesWithListOfJavaType() { - RuntimeHints runtimeHints = process(SamplePropertiesWithSimpleList.class); - List typeHints = runtimeHints.reflection().typeHints().toList(); - assertThat(typeHints).anySatisfy(javaBeanBinding(SamplePropertiesWithSimpleList.class)); - assertThat(typeHints).hasSize(2); - } - - @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(2); - } - @Test void processValueObjectConfigurationPropertiesWithSpecificConstructor() throws NoSuchMethodException { RuntimeHints runtimeHints = process(SampleImmutablePropertiesWithSeveralConstructors.class); @@ -156,17 +91,6 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests { assertThat(typeHints).hasSize(2); } - @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(4); - } - @Test void processConfigurationPropertiesWithNestedTypeNotUsedIsIgnored() { RuntimeHints runtimeHints = process(SamplePropertiesWithNested.class); @@ -174,23 +98,6 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests { .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(4); - } - - @Test - void processConfigurationPropertiesWithRecursiveType() { - RuntimeHints runtimeHints = process(SamplePropertiesWithRecursive.class); - assertThat(runtimeHints.reflection().typeHints()) - .anySatisfy(javaBeanBinding(SamplePropertiesWithRecursive.class)) - .anySatisfy(javaBeanBinding(Recursive.class)).hasSize(3); - } - @Test void processValueObjectConfigurationPropertiesWithRecursiveType() { RuntimeHints runtimeHints = process(SampleImmutablePropertiesWithRecursive.class); @@ -202,15 +109,6 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests { .hasSize(3); } - @Test - void processConfigurationPropertiesWithWellKnownTypes() { - RuntimeHints runtimeHints = process(SamplePropertiesWithWellKnownTypes.class); - assertThat(runtimeHints.reflection().typeHints()) - .anySatisfy(javaBeanBinding(SamplePropertiesWithWellKnownTypes.class)) - // TODO - .hasSize(2); - } - @Test void processConfigurationPropertiesWithCrossReference() { RuntimeHints runtimeHints = process(SamplePropertiesWithCrossReference.class); @@ -279,65 +177,6 @@ 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 { @@ -355,18 +194,6 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests { } - @ConfigurationProperties - public static class SampleImmutablePropertiesWithList { - - @SuppressWarnings("unused") - private final List family; - - SampleImmutablePropertiesWithList(List family) { - this.family = family; - } - - } - @ConfigurationProperties("nested") public static class SamplePropertiesWithNested { @@ -376,48 +203,6 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests { } - @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 { @@ -430,84 +215,6 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests { } - @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") diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessorTests.java new file mode 100644 index 0000000000..099a49f16c --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessorTests.java @@ -0,0 +1,437 @@ +/* + * Copyright 2012-2022 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.boot.context.properties; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.Environment; +import org.springframework.util.ReflectionUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +/** + * Tests for {@link ConfigurationPropertiesReflectionHintsProcessor}. + * + * @author Moritz Halbritter + */ +class ConfigurationPropertiesReflectionHintsProcessorTests { + + @Test + void shouldRegisterNoArgConstructor() throws NoSuchMethodException { + RuntimeHints hints = runProcessor(NoArgsCtorProperties.class); + assertThat(RuntimeHintsPredicates.reflection() + .onConstructor(ReflectionUtils.accessibleConstructor(NoArgsCtorProperties.class))).accepts(hints); + } + + @Test + void shouldRegisterMemberCategories() { + RuntimeHints hints = runProcessor(NoArgsCtorProperties.class); + hasReflectionHintsForType(hints, NoArgsCtorProperties.class); + } + + @Test + void shouldNotRegisterJavaTypes() { + RuntimeHints hints = runProcessor(NoArgsCtorProperties.class); + assertThat(RuntimeHintsPredicates.reflection().onType(String.class)).rejects(hints); + } + + @Test + void shouldNotRegisterJavaTypesInLists() { + RuntimeHints hints = runProcessor(StringListProperties.class); + assertThat(RuntimeHintsPredicates.reflection().onType(String.class)).rejects(hints); + } + + @Test + void shouldRegisterConstructorBinding() throws NoSuchMethodException { + RuntimeHints hints = runProcessor(ConstructorBindingProperties.class); + assertThat(RuntimeHintsPredicates.reflection() + .onConstructor(ReflectionUtils.accessibleConstructor(ConstructorBindingProperties.class, String.class))) + .accepts(hints); + } + + @Test + void shouldRegisterConstructorBindingWithRecords() throws NoSuchMethodException { + RuntimeHints hints = runProcessor(RecordProperties.class); + assertThat(RuntimeHintsPredicates.reflection() + .onConstructor(ReflectionUtils.accessibleConstructor(RecordProperties.class, String.class))) + .accepts(hints); + } + + @Test + void shouldRegisterNestedTypes() { + RuntimeHints hints = runProcessor(NestedProperties.class); + hasReflectionHintsForType(hints, Nested.class); + } + + @Test + void shouldRegisterNestedTypesWhenNoSetterIsPresent() { + RuntimeHints hints = runProcessor(NestedNoSetterProperties.class); + hasReflectionHintsForType(hints, Nested.class); + } + + @Test + void shouldRegisterNestedTypesInLists() { + RuntimeHints hints = runProcessor(NestedListProperties.class); + hasReflectionHintsForType(hints, Nested.class); + } + + @Test + void shouldRegisterNestedTypesInMaps() { + RuntimeHints hints = runProcessor(NestedMapProperties.class); + hasReflectionHintsForType(hints, Nested.class); + } + + @Test + void shouldRegisterNestedTypesInArrays() { + RuntimeHints hints = runProcessor(NestedArrayProperties.class); + hasReflectionHintsForType(hints, Nested.class); + } + + @Test + void shouldRegisterNestedWithRecords() { + RuntimeHints hints = runProcessor(NestedRecordProperties.class); + hasReflectionHintsForType(hints, Nested.class); + } + + @Test + void shouldRegisterMultipleLevelsOfNested() { + RuntimeHints hints = runProcessor(NestedMultipleLevelsProperties.class); + hasReflectionHintsForType(hints, NestedLevel1.class); + hasReflectionHintsForType(hints, NestedLevel2.class); + hasReflectionHintsForType(hints, NestedLevel3.class); + } + + @Test + void shouldNotCrashOnCycles() { + assertThatCode(() -> runProcessor(CycleProperties.class)).doesNotThrowAnyException(); + } + + @Test + void shouldNotRegisterInterfaces() { + RuntimeHints hints = runProcessor(InterfaceProperties.class); + assertThat(RuntimeHintsPredicates.reflection().onType(SomeInterface.class)).rejects(hints); + } + + @Test + void shouldNotRegisterKnownTypes() { + RuntimeHints hints = runProcessor(AwareProperties.class); + assertThat(RuntimeHintsPredicates.reflection().onType(Environment.class)).rejects(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(ApplicationContext.class)).rejects(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(BeanFactory.class)).rejects(hints); + } + + private RuntimeHints runProcessor(Class type) { + RuntimeHints hints = new RuntimeHints(); + ConfigurationPropertiesReflectionHintsProcessor.processConfigurationProperties(type, hints.reflection()); + return hints; + } + + private void hasReflectionHintsForType(RuntimeHints hints, Class type) { + assertThat(RuntimeHintsPredicates.reflection().onType(type) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS)) + .accepts(hints); + } + + @ConfigurationProperties + public static class NoArgsCtorProperties { + + private String field; + + NoArgsCtorProperties() { + } + + NoArgsCtorProperties(String field) { + this.field = field; + } + + public String getField() { + return this.field; + } + + public void setField(String field) { + this.field = field; + } + + } + + @ConfigurationProperties + public static class StringListProperties { + + private final List stringList = new ArrayList<>(); + + public List getStringList() { + return this.stringList; + } + + } + + @ConfigurationProperties + public static class ConstructorBindingProperties { + + private final String field; + + ConstructorBindingProperties(String field) { + this.field = field; + } + + public String getField() { + return this.field; + } + + } + + @ConfigurationProperties + public record RecordProperties(String field) { + } + + @ConfigurationProperties + public static class NestedProperties { + + private Nested nested; + + public Nested getNested() { + return this.nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + + } + + @ConfigurationProperties + public static class NestedNoSetterProperties { + + private final Nested nested = new Nested(); + + public Nested getNested() { + return this.nested; + } + + } + + @ConfigurationProperties + public static class NestedListProperties { + + private List nestedList; + + public List getNestedList() { + return this.nestedList; + } + + public void setNestedList(List nestedList) { + this.nestedList = nestedList; + } + + } + + @ConfigurationProperties + public static class NestedMapProperties { + + private Map nestedMap = new HashMap<>(); + + public Map getNestedMap() { + return this.nestedMap; + } + + } + + @ConfigurationProperties + public static class NestedArrayProperties { + + private Nested[] nestedArray; + + public Nested[] getNestedArray() { + return this.nestedArray; + } + + public void setNestedArray(Nested[] nestedArray) { + this.nestedArray = nestedArray; + } + + } + + @ConfigurationProperties + public record NestedRecordProperties(Nested nested) { + + } + + @ConfigurationProperties + public static class CycleProperties { + + private CycleProperties cycleProperties; + + public CycleProperties getCycleProperties() { + return this.cycleProperties; + } + + public void setCycleProperties(CycleProperties cycleProperties) { + this.cycleProperties = cycleProperties; + } + + } + + @ConfigurationProperties + public static class NestedMultipleLevelsProperties { + + private NestedLevel1 nestedLevel1; + + public NestedLevel1 getNestedLevel1() { + return this.nestedLevel1; + } + + public void setNestedLevel1(NestedLevel1 nestedLevel1) { + this.nestedLevel1 = nestedLevel1; + } + + } + + @ConfigurationProperties + public static class InterfaceProperties { + + private SomeInterface someInterface; + + public SomeInterface getSomeInterface() { + return this.someInterface; + } + + public void setSomeInterface(SomeInterface someInterface) { + this.someInterface = someInterface; + } + + } + + @ConfigurationProperties + public static class AwareProperties implements ApplicationContextAware, BeanFactoryAware, EnvironmentAware { + + private String field; + + private BeanFactory beanFactory; + + private ApplicationContext applicationContext; + + private Environment environment; + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + public String getField() { + return this.field; + } + + public BeanFactory getBeanFactory() { + return this.beanFactory; + } + + public ApplicationContext getApplicationContext() { + return this.applicationContext; + } + + public Environment getEnvironment() { + return this.environment; + } + + } + + interface SomeInterface { + + } + + public static class Nested { + + private String field; + + public String getField() { + return this.field; + } + + public void setField(String field) { + this.field = field; + } + + } + + public static class NestedLevel1 { + + private NestedLevel2 nestedLevel2; + + public NestedLevel2 getNestedLevel2() { + return this.nestedLevel2; + } + + public void setNestedLevel2(NestedLevel2 nestedLevel2) { + this.nestedLevel2 = nestedLevel2; + } + + } + + public static class NestedLevel2 { + + private NestedLevel3 nestedLevel3; + + public NestedLevel3 getNestedLevel3() { + return this.nestedLevel3; + } + + public void setNestedLevel3(NestedLevel3 nestedLevel3) { + this.nestedLevel3 = nestedLevel3; + } + + } + + public class NestedLevel3 { + + private String field; + + public String getField() { + return this.field; + } + + public void setField(String field) { + this.field = field; + } + + } + +}