Generate hints for nested generics in configuration properties
See gh-31708
This commit is contained in:
parent
57dc274284
commit
35c49afd97
|
@ -163,7 +163,7 @@ public final class ConfigurationPropertiesReflectionHintsProcessor {
|
|||
if (propertyClass == null) {
|
||||
return true;
|
||||
}
|
||||
if (getComponentType(propertyType) != null) {
|
||||
if (isContainer(propertyType)) {
|
||||
return false;
|
||||
}
|
||||
return !isNestedType(propertyName, propertyClass);
|
||||
|
@ -171,21 +171,43 @@ public final class ConfigurationPropertiesReflectionHintsProcessor {
|
|||
|
||||
private Class<?> getComponentType(ResolvableType propertyType) {
|
||||
Class<?> propertyClass = propertyType.toClass();
|
||||
ResolvableType componentType = null;
|
||||
if (propertyType.isArray()) {
|
||||
return propertyType.getComponentType().toClass();
|
||||
componentType = propertyType.getComponentType();
|
||||
}
|
||||
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)) {
|
||||
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
|
||||
* 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 propertyType the type of the property
|
||||
* @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.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.TypeHint;
|
||||
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.BeanFactoryInitializationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
|
||||
|
@ -53,6 +55,7 @@ import static org.mockito.Mockito.mock;
|
|||
* Tests for {@link ConfigurationPropertiesBeanFactoryInitializationAotProcessor}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
|
||||
|
||||
|
@ -227,6 +230,31 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
|
|||
.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) {
|
||||
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…
Reference in New Issue