Skip to content

Commit f3450fd

Browse files
committed
Detect nested types that are not directly nested to the current type
This commit makes sure that sub-namespace that are defined in a flat manner in a configuration properties are considered for runtime hints Closes gh-36909
1 parent 87569a0 commit f3450fd

File tree

2 files changed

+76
-1
lines changed

2 files changed

+76
-1
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,13 +318,20 @@ private boolean isMap(ResolvableType type) {
318318
*/
319319
private boolean isNestedType(String propertyName, Class<?> propertyType) {
320320
Class<?> declaringClass = propertyType.getDeclaringClass();
321-
if (declaringClass != null && declaringClass.isAssignableFrom(this.type)) {
321+
if (declaringClass != null && isNested(declaringClass, this.type)) {
322322
return true;
323323
}
324324
Field field = ReflectionUtils.findField(this.type, propertyName);
325325
return (field != null) && MergedAnnotations.from(field).isPresent(Nested.class);
326326
}
327327

328+
private static boolean isNested(Class<?> type, Class<?> candidate) {
329+
if (type.isAssignableFrom(candidate)) {
330+
return true;
331+
}
332+
return (candidate.getDeclaringClass() != null && isNested(type, candidate.getDeclaringClass()));
333+
}
334+
328335
private boolean isJavaType(Class<?> candidate) {
329336
return candidate.getPackageName().startsWith("java.");
330337
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrarTests.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
import org.springframework.boot.context.properties.ConfigurationPropertiesBean;
3636
import org.springframework.boot.context.properties.NestedConfigurationProperty;
3737
import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrarTests.BaseProperties.InheritedNested;
38+
import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrarTests.ComplexNestedProperties.ListenerRetry;
39+
import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrarTests.ComplexNestedProperties.Retry;
40+
import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrarTests.ComplexNestedProperties.Simple;
3841
import org.springframework.context.ApplicationContext;
3942
import org.springframework.context.ApplicationContextAware;
4043
import org.springframework.context.EnvironmentAware;
@@ -259,6 +262,23 @@ void registerHintsWhenHasInheritedNestedProperties() {
259262
.satisfies(javaBeanBinding(InheritedNested.class, "getAlpha", "setAlpha"));
260263
}
261264

265+
@Test
266+
void registerHintsWhenHasComplexNestedProperties() {
267+
RuntimeHints runtimeHints = registerHints(ComplexNestedProperties.class);
268+
assertThat(runtimeHints.reflection().typeHints()).hasSize(4);
269+
assertThat(runtimeHints.reflection().getTypeHint(Retry.class)).satisfies((entry) -> {
270+
assertThat(entry.getMemberCategories()).isEmpty();
271+
assertThat(entry.methods()).extracting(ExecutableHint::getName)
272+
.containsExactlyInAnyOrder("getCount", "setCount");
273+
});
274+
assertThat(runtimeHints.reflection().getTypeHint(ListenerRetry.class))
275+
.satisfies(javaBeanBinding(ListenerRetry.class, "isStateless", "setStateless"));
276+
assertThat(runtimeHints.reflection().getTypeHint(Simple.class))
277+
.satisfies(javaBeanBinding(Simple.class, "getRetry"));
278+
assertThat(runtimeHints.reflection().getTypeHint(ComplexNestedProperties.class))
279+
.satisfies(javaBeanBinding(ComplexNestedProperties.class, "getSimple"));
280+
}
281+
262282
private Consumer<TypeHint> javaBeanBinding(Class<?> type, String... expectedMethods) {
263283
return javaBeanBinding(type, type.getDeclaredConstructors()[0], expectedMethods);
264284
}
@@ -723,4 +743,52 @@ public void setBravo(String bravo) {
723743

724744
}
725745

746+
public static class ComplexNestedProperties {
747+
748+
private final Simple simple = new Simple();
749+
750+
public Simple getSimple() {
751+
return this.simple;
752+
}
753+
754+
public static class Simple {
755+
756+
private final ListenerRetry retry = new ListenerRetry();
757+
758+
public ListenerRetry getRetry() {
759+
return this.retry;
760+
}
761+
762+
}
763+
764+
public abstract static class Retry {
765+
766+
private int count = 5;
767+
768+
public int getCount() {
769+
return this.count;
770+
}
771+
772+
public void setCount(int count) {
773+
this.count = count;
774+
}
775+
776+
}
777+
778+
public static class ListenerRetry extends Retry {
779+
780+
private boolean stateless;
781+
782+
public boolean isStateless() {
783+
return this.stateless;
784+
}
785+
786+
public void setStateless(boolean stateless) {
787+
this.stateless = stateless;
788+
}
789+
790+
}
791+
792+
}
793+
726794
}

0 commit comments

Comments
 (0)