diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java index 40fbba7d7a..c2ce25d842 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java @@ -318,13 +318,20 @@ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { */ private boolean isNestedType(String propertyName, Class propertyType) { Class declaringClass = propertyType.getDeclaringClass(); - if (declaringClass != null && declaringClass.isAssignableFrom(this.type)) { + if (declaringClass != null && isNested(declaringClass, this.type)) { return true; } Field field = ReflectionUtils.findField(this.type, propertyName); return (field != null) && MergedAnnotations.from(field).isPresent(Nested.class); } + private static boolean isNested(Class type, Class candidate) { + if (type.isAssignableFrom(candidate)) { + return true; + } + return (candidate.getDeclaringClass() != null && isNested(type, candidate.getDeclaringClass())); + } + private boolean isJavaType(Class candidate) { return candidate.getPackageName().startsWith("java."); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrarTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrarTests.java index b1b4f8bbfb..96a40a5ff9 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrarTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrarTests.java @@ -35,6 +35,9 @@ import org.springframework.boot.context.properties.BoundConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationPropertiesBean; import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrarTests.BaseProperties.InheritedNested; +import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrarTests.ComplexNestedProperties.ListenerRetry; +import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrarTests.ComplexNestedProperties.Retry; +import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrarTests.ComplexNestedProperties.Simple; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.EnvironmentAware; @@ -259,6 +262,23 @@ class BindableRuntimeHintsRegistrarTests { .satisfies(javaBeanBinding(InheritedNested.class, "getAlpha", "setAlpha")); } + @Test + void registerHintsWhenHasComplexNestedProperties() { + RuntimeHints runtimeHints = registerHints(ComplexNestedProperties.class); + assertThat(runtimeHints.reflection().typeHints()).hasSize(4); + assertThat(runtimeHints.reflection().getTypeHint(Retry.class)).satisfies((entry) -> { + assertThat(entry.getMemberCategories()).isEmpty(); + assertThat(entry.methods()).extracting(ExecutableHint::getName) + .containsExactlyInAnyOrder("getCount", "setCount"); + }); + assertThat(runtimeHints.reflection().getTypeHint(ListenerRetry.class)) + .satisfies(javaBeanBinding(ListenerRetry.class, "isStateless", "setStateless")); + assertThat(runtimeHints.reflection().getTypeHint(Simple.class)) + .satisfies(javaBeanBinding(Simple.class, "getRetry")); + assertThat(runtimeHints.reflection().getTypeHint(ComplexNestedProperties.class)) + .satisfies(javaBeanBinding(ComplexNestedProperties.class, "getSimple")); + } + private Consumer javaBeanBinding(Class type, String... expectedMethods) { return javaBeanBinding(type, type.getDeclaredConstructors()[0], expectedMethods); } @@ -723,4 +743,52 @@ class BindableRuntimeHintsRegistrarTests { } + public static class ComplexNestedProperties { + + private final Simple simple = new Simple(); + + public Simple getSimple() { + return this.simple; + } + + public static class Simple { + + private final ListenerRetry retry = new ListenerRetry(); + + public ListenerRetry getRetry() { + return this.retry; + } + + } + + public abstract static class Retry { + + private int count = 5; + + public int getCount() { + return this.count; + } + + public void setCount(int count) { + this.count = count; + } + + } + + public static class ListenerRetry extends Retry { + + private boolean stateless; + + public boolean isStateless() { + return this.stateless; + } + + public void setStateless(boolean stateless) { + this.stateless = stateless; + } + + } + + } + }