Generate hints for nested generics in configuration properties

See gh-31708
pull/31845/head
Moritz Halbritter 2 years ago
parent 57dc274284
commit 35c49afd97

@ -163,7 +163,7 @@ public final class ConfigurationPropertiesReflectionHintsProcessor {
if (propertyClass == null) { if (propertyClass == null) {
return true; return true;
} }
if (getComponentType(propertyType) != null) { if (isContainer(propertyType)) {
return false; return false;
} }
return !isNestedType(propertyName, propertyClass); return !isNestedType(propertyName, propertyClass);
@ -171,21 +171,43 @@ public final class ConfigurationPropertiesReflectionHintsProcessor {
private Class<?> getComponentType(ResolvableType propertyType) { private Class<?> getComponentType(ResolvableType propertyType) {
Class<?> propertyClass = propertyType.toClass(); Class<?> propertyClass = propertyType.toClass();
ResolvableType componentType = null;
if (propertyType.isArray()) { if (propertyType.isArray()) {
return propertyType.getComponentType().toClass(); componentType = propertyType.getComponentType();
} }
else if (Collection.class.isAssignableFrom(propertyClass)) { else if (Collection.class.isAssignableFrom(propertyClass)) {
return propertyType.as(Collection.class).getGeneric(0).toClass(); componentType = propertyType.asCollection().getGeneric(0);
} }
else if (Map.class.isAssignableFrom(propertyClass)) { else if (Map.class.isAssignableFrom(propertyClass)) {
return propertyType.as(Map.class).getGeneric(1).toClass(); componentType = propertyType.asMap().getGeneric(1);
} }
return null; if (componentType == null) {
return null;
}
if (isContainer(componentType)) {
// Resolve nested generics like Map<String, List<SomeType>>
return getComponentType(componentType);
}
return componentType.toClass();
}
private boolean isContainer(ResolvableType type) {
if (type.isArray()) {
return true;
}
if (Collection.class.isAssignableFrom(type.toClass())) {
return true;
}
else if (Map.class.isAssignableFrom(type.toClass())) {
return true;
}
return false;
} }
/** /**
* Specify whether the specified property refer to a nested type. A nested type * Specify whether the specified property refer to a nested type. A nested type
* represents a sub-namespace that need to be fully resolved. * represents a sub-namespace that need to be fully resolved. Nested types are either
* inner classes or annotated with {@link NestedConfigurationProperty}.
* @param propertyName the name of the property * @param propertyName the name of the property
* @param propertyType the type of the property * @param propertyType the type of the property
* @return whether the specified {@code propertyType} is a nested type * @return whether the specified {@code propertyType} is a nested type

@ -19,6 +19,7 @@ package org.springframework.boot.context.properties;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -34,6 +35,7 @@ import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeHint; import org.springframework.aot.hint.TypeHint;
import org.springframework.aot.hint.TypeReference; import org.springframework.aot.hint.TypeReference;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.beans.factory.aot.AotFactoriesLoader; import org.springframework.beans.factory.aot.AotFactoriesLoader;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
@ -53,6 +55,7 @@ import static org.mockito.Mockito.mock;
* Tests for {@link ConfigurationPropertiesBeanFactoryInitializationAotProcessor}. * Tests for {@link ConfigurationPropertiesBeanFactoryInitializationAotProcessor}.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Moritz Halbritter
*/ */
class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests { class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
@ -227,6 +230,31 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
.anySatisfy(javaBeanBinding(GenericObject.class)); .anySatisfy(javaBeanBinding(GenericObject.class));
} }
@Test
void processConfigurationPropertiesWithNestedGenerics() {
RuntimeHints runtimeHints = process(NestedGenerics.class);
assertThat(RuntimeHintsPredicates.reflection().onType(NestedGenerics.class)
.withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS))
.accepts(runtimeHints);
assertThat(RuntimeHintsPredicates.reflection().onType(NestedGenerics.Nested.class)
.withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS))
.accepts(runtimeHints);
}
@Test
void processConfigurationPropertiesWithMultipleNestedClasses() {
RuntimeHints runtimeHints = process(TripleNested.class);
assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.class)
.withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS))
.accepts(runtimeHints);
assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.DoubleNested.class)
.withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS))
.accepts(runtimeHints);
assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.DoubleNested.Nested.class)
.withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS))
.accepts(runtimeHints);
}
private Consumer<TypeHint> javaBeanBinding(Class<?> type) { private Consumer<TypeHint> javaBeanBinding(Class<?> type) {
return javaBeanBinding(type, type.getDeclaredConstructors()[0]); return javaBeanBinding(type, type.getDeclaredConstructors()[0]);
} }
@ -590,4 +618,64 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
} }
@ConfigurationProperties(prefix = "nested-generics")
public static class NestedGenerics {
private final Map<String, List<Nested>> nested = new HashMap<>();
public Map<String, List<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;
}
}
}
@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;
}
}
}
}
} }

Loading…
Cancel
Save