Skip to content

Commit 35c49af

Browse files
committed
Generate hints for nested generics in configuration properties
See gh-31708
1 parent 57dc274 commit 35c49af

File tree

2 files changed

+116
-6
lines changed

2 files changed

+116
-6
lines changed

Diff for: spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessor.java

+28-6
Original file line numberDiff line numberDiff line change
@@ -163,29 +163,51 @@ private boolean isSetterMandatory(String propertyName, ResolvableType propertyTy
163163
if (propertyClass == null) {
164164
return true;
165165
}
166-
if (getComponentType(propertyType) != null) {
166+
if (isContainer(propertyType)) {
167167
return false;
168168
}
169169
return !isNestedType(propertyName, propertyClass);
170170
}
171171

172172
private Class<?> getComponentType(ResolvableType propertyType) {
173173
Class<?> propertyClass = propertyType.toClass();
174+
ResolvableType componentType = null;
174175
if (propertyType.isArray()) {
175-
return propertyType.getComponentType().toClass();
176+
componentType = propertyType.getComponentType();
176177
}
177178
else if (Collection.class.isAssignableFrom(propertyClass)) {
178-
return propertyType.as(Collection.class).getGeneric(0).toClass();
179+
componentType = propertyType.asCollection().getGeneric(0);
179180
}
180181
else if (Map.class.isAssignableFrom(propertyClass)) {
181-
return propertyType.as(Map.class).getGeneric(1).toClass();
182+
componentType = propertyType.asMap().getGeneric(1);
182183
}
183-
return null;
184+
if (componentType == null) {
185+
return null;
186+
}
187+
if (isContainer(componentType)) {
188+
// Resolve nested generics like Map<String, List<SomeType>>
189+
return getComponentType(componentType);
190+
}
191+
return componentType.toClass();
192+
}
193+
194+
private boolean isContainer(ResolvableType type) {
195+
if (type.isArray()) {
196+
return true;
197+
}
198+
if (Collection.class.isAssignableFrom(type.toClass())) {
199+
return true;
200+
}
201+
else if (Map.class.isAssignableFrom(type.toClass())) {
202+
return true;
203+
}
204+
return false;
184205
}
185206

186207
/**
187208
* Specify whether the specified property refer to a nested type. A nested type
188-
* represents a sub-namespace that need to be fully resolved.
209+
* represents a sub-namespace that need to be fully resolved. Nested types are either
210+
* inner classes or annotated with {@link NestedConfigurationProperty}.
189211
* @param propertyName the name of the property
190212
* @param propertyType the type of the property
191213
* @return whether the specified {@code propertyType} is a nested type

Diff for: spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java

+88
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.lang.reflect.Constructor;
2020
import java.util.Arrays;
2121
import java.util.Collections;
22+
import java.util.HashMap;
2223
import java.util.List;
2324
import java.util.Map;
2425
import java.util.function.Consumer;
@@ -34,6 +35,7 @@
3435
import org.springframework.aot.hint.RuntimeHints;
3536
import org.springframework.aot.hint.TypeHint;
3637
import org.springframework.aot.hint.TypeReference;
38+
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
3739
import org.springframework.beans.factory.aot.AotFactoriesLoader;
3840
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
3941
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
@@ -53,6 +55,7 @@
5355
* Tests for {@link ConfigurationPropertiesBeanFactoryInitializationAotProcessor}.
5456
*
5557
* @author Stephane Nicoll
58+
* @author Moritz Halbritter
5659
*/
5760
class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
5861

@@ -227,6 +230,31 @@ void processConfigurationPropertiesWithUnresolvedGeneric() {
227230
.anySatisfy(javaBeanBinding(GenericObject.class));
228231
}
229232

233+
@Test
234+
void processConfigurationPropertiesWithNestedGenerics() {
235+
RuntimeHints runtimeHints = process(NestedGenerics.class);
236+
assertThat(RuntimeHintsPredicates.reflection().onType(NestedGenerics.class)
237+
.withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS))
238+
.accepts(runtimeHints);
239+
assertThat(RuntimeHintsPredicates.reflection().onType(NestedGenerics.Nested.class)
240+
.withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS))
241+
.accepts(runtimeHints);
242+
}
243+
244+
@Test
245+
void processConfigurationPropertiesWithMultipleNestedClasses() {
246+
RuntimeHints runtimeHints = process(TripleNested.class);
247+
assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.class)
248+
.withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS))
249+
.accepts(runtimeHints);
250+
assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.DoubleNested.class)
251+
.withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS))
252+
.accepts(runtimeHints);
253+
assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.DoubleNested.Nested.class)
254+
.withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS))
255+
.accepts(runtimeHints);
256+
}
257+
230258
private Consumer<TypeHint> javaBeanBinding(Class<?> type) {
231259
return javaBeanBinding(type, type.getDeclaredConstructors()[0]);
232260
}
@@ -590,4 +618,64 @@ public T getValue() {
590618

591619
}
592620

621+
@ConfigurationProperties(prefix = "nested-generics")
622+
public static class NestedGenerics {
623+
624+
private final Map<String, List<Nested>> nested = new HashMap<>();
625+
626+
public Map<String, List<Nested>> getNested() {
627+
return this.nested;
628+
}
629+
630+
public static class Nested {
631+
632+
private String field;
633+
634+
public String getField() {
635+
return this.field;
636+
}
637+
638+
public void setField(String field) {
639+
this.field = field;
640+
}
641+
642+
}
643+
644+
}
645+
646+
@ConfigurationProperties(prefix = "triple-nested")
647+
public static class TripleNested {
648+
649+
private final DoubleNested doubleNested = new DoubleNested();
650+
651+
public DoubleNested getDoubleNested() {
652+
return this.doubleNested;
653+
}
654+
655+
public static class DoubleNested {
656+
657+
private final Nested nested = new Nested();
658+
659+
public Nested getNested() {
660+
return this.nested;
661+
}
662+
663+
public static class Nested {
664+
665+
private String field;
666+
667+
public String getField() {
668+
return this.field;
669+
}
670+
671+
public void setField(String field) {
672+
this.field = field;
673+
}
674+
675+
}
676+
677+
}
678+
679+
}
680+
593681
}

0 commit comments

Comments
 (0)