Browse Source
Relocate `ConfigurationPropertiesReflectionHintsProcessor` and refactor it to be a general purpose `BindableRuntimeHintsRegistrar`. Prior to this commit, `ConfigurationPropertiesReflectionHintsProcessor` was used to declare binding hints for classes that were bound, but might be `@ConfigurationProperties`. By moving and renaming the class, it's now better aligned to the way it's used. Support for `@NestedConfigurationProperties` has been implemented by adding a `@Nestable` meta-annotation. This allow us to create the appropriate hints, without the `Binder` needing to be directly aware of the `@NestedConfigurationProperties` annotation. Closes gh-32815pull/32840/head
9 changed files with 328 additions and 283 deletions
@ -1,252 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2012-2022 the original author or authors. |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
|
|
||||||
package org.springframework.boot.context.properties; |
|
||||||
|
|
||||||
import java.beans.BeanInfo; |
|
||||||
import java.beans.IntrospectionException; |
|
||||||
import java.beans.Introspector; |
|
||||||
import java.beans.PropertyDescriptor; |
|
||||||
import java.lang.reflect.Constructor; |
|
||||||
import java.lang.reflect.Field; |
|
||||||
import java.lang.reflect.Method; |
|
||||||
import java.util.Arrays; |
|
||||||
import java.util.Collection; |
|
||||||
import java.util.HashSet; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.Set; |
|
||||||
|
|
||||||
import org.springframework.aot.hint.ExecutableMode; |
|
||||||
import org.springframework.aot.hint.ReflectionHints; |
|
||||||
import org.springframework.beans.BeanInfoFactory; |
|
||||||
import org.springframework.beans.ExtendedBeanInfoFactory; |
|
||||||
import org.springframework.boot.context.properties.bind.BindConstructorProvider; |
|
||||||
import org.springframework.boot.context.properties.bind.Bindable; |
|
||||||
import org.springframework.core.ResolvableType; |
|
||||||
import org.springframework.core.annotation.MergedAnnotations; |
|
||||||
import org.springframework.util.ReflectionUtils; |
|
||||||
|
|
||||||
/** |
|
||||||
* Registers a given type on {@link ReflectionHints} for binding purposes, discovering any |
|
||||||
* nested type it may expose via a property. |
|
||||||
* |
|
||||||
* @author Andy Wilkinson |
|
||||||
* @author Moritz Halbritter |
|
||||||
* @author Sebastien Deleuze |
|
||||||
* @since 3.0.0 |
|
||||||
*/ |
|
||||||
public final class ConfigurationPropertiesReflectionHintsProcessor { |
|
||||||
|
|
||||||
private static final BeanInfoFactory beanInfoFactory = new ExtendedBeanInfoFactory(); |
|
||||||
|
|
||||||
private final Class<?> type; |
|
||||||
|
|
||||||
private final Constructor<?> bindConstructor; |
|
||||||
|
|
||||||
private final BeanInfo beanInfo; |
|
||||||
|
|
||||||
private final Set<Class<?>> seen; |
|
||||||
|
|
||||||
private ConfigurationPropertiesReflectionHintsProcessor(Class<?> type, Constructor<?> bindConstructor, |
|
||||||
Set<Class<?>> seen) { |
|
||||||
this.type = type; |
|
||||||
this.bindConstructor = bindConstructor; |
|
||||||
this.beanInfo = getBeanInfo(type); |
|
||||||
this.seen = seen; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Registers a given type on {@link ReflectionHints} for binding purposes, discovering |
|
||||||
* any nested type it may expose via a property. |
|
||||||
* @param type type to process |
|
||||||
* @param reflectionHints {@link ReflectionHints} to register the types on |
|
||||||
*/ |
|
||||||
public static void processConfigurationProperties(Class<?> type, ReflectionHints reflectionHints) { |
|
||||||
new ConfigurationPropertiesReflectionHintsProcessor(type, getBindConstructor(type, false), new HashSet<>()) |
|
||||||
.process(reflectionHints); |
|
||||||
} |
|
||||||
|
|
||||||
private void processNestedType(Class<?> type, ReflectionHints reflectionHints) { |
|
||||||
processNestedType(type, getBindConstructor(type, true), reflectionHints); |
|
||||||
} |
|
||||||
|
|
||||||
private void processNestedType(Class<?> type, Constructor<?> bindConstructor, ReflectionHints reflectionHints) { |
|
||||||
new ConfigurationPropertiesReflectionHintsProcessor(type, bindConstructor, this.seen).process(reflectionHints); |
|
||||||
} |
|
||||||
|
|
||||||
private static Constructor<?> getBindConstructor(Class<?> type, boolean nestedType) { |
|
||||||
Bindable<?> bindable = Bindable.of(type); |
|
||||||
return BindConstructorProvider.DEFAULT.getBindConstructor(bindable, nestedType); |
|
||||||
} |
|
||||||
|
|
||||||
private void process(ReflectionHints reflectionHints) { |
|
||||||
if (this.seen.contains(this.type)) { |
|
||||||
return; |
|
||||||
} |
|
||||||
this.seen.add(this.type); |
|
||||||
handleConstructor(reflectionHints); |
|
||||||
if (this.bindConstructor != null) { |
|
||||||
handleValueObjectProperties(reflectionHints); |
|
||||||
} |
|
||||||
else if (this.beanInfo != null) { |
|
||||||
handleJavaBeanProperties(reflectionHints); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void handleConstructor(ReflectionHints reflectionHints) { |
|
||||||
if (this.bindConstructor != null) { |
|
||||||
reflectionHints.registerConstructor(this.bindConstructor, ExecutableMode.INVOKE); |
|
||||||
return; |
|
||||||
} |
|
||||||
Arrays.stream(this.type.getDeclaredConstructors()).filter(this::hasNoParameters).findFirst() |
|
||||||
.ifPresent((constructor) -> reflectionHints.registerConstructor(constructor, ExecutableMode.INVOKE)); |
|
||||||
} |
|
||||||
|
|
||||||
private boolean hasNoParameters(Constructor<?> candidate) { |
|
||||||
return candidate.getParameterCount() == 0; |
|
||||||
} |
|
||||||
|
|
||||||
private void handleValueObjectProperties(ReflectionHints reflectionHints) { |
|
||||||
for (int i = 0; i < this.bindConstructor.getParameterCount(); i++) { |
|
||||||
String propertyName = this.bindConstructor.getParameters()[i].getName(); |
|
||||||
ResolvableType propertyType = ResolvableType.forConstructorParameter(this.bindConstructor, i); |
|
||||||
handleProperty(reflectionHints, propertyName, propertyType); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void handleJavaBeanProperties(ReflectionHints reflectionHints) { |
|
||||||
for (PropertyDescriptor propertyDescriptor : this.beanInfo.getPropertyDescriptors()) { |
|
||||||
Method writeMethod = propertyDescriptor.getWriteMethod(); |
|
||||||
if (writeMethod != null) { |
|
||||||
reflectionHints.registerMethod(writeMethod, ExecutableMode.INVOKE); |
|
||||||
} |
|
||||||
Method readMethod = propertyDescriptor.getReadMethod(); |
|
||||||
if (readMethod != null) { |
|
||||||
ResolvableType propertyType = ResolvableType.forMethodReturnType(readMethod, this.type); |
|
||||||
String propertyName = propertyDescriptor.getName(); |
|
||||||
if (isSetterMandatory(propertyName, propertyType) && writeMethod == null) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
handleProperty(reflectionHints, propertyName, propertyType); |
|
||||||
reflectionHints.registerMethod(readMethod, ExecutableMode.INVOKE); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private boolean isSetterMandatory(String propertyName, ResolvableType propertyType) { |
|
||||||
Class<?> propertyClass = propertyType.resolve(); |
|
||||||
if (propertyClass == null) { |
|
||||||
return true; |
|
||||||
} |
|
||||||
if (isContainer(propertyType)) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
return !isNestedType(propertyName, propertyClass); |
|
||||||
} |
|
||||||
|
|
||||||
private void handleProperty(ReflectionHints reflectionHints, String propertyName, ResolvableType propertyType) { |
|
||||||
Class<?> propertyClass = propertyType.resolve(); |
|
||||||
if (propertyClass == null) { |
|
||||||
return; |
|
||||||
} |
|
||||||
if (propertyClass.equals(this.type)) { |
|
||||||
return; // Prevent infinite recursion
|
|
||||||
} |
|
||||||
Class<?> componentType = getComponentClass(propertyType); |
|
||||||
if (componentType != null) { |
|
||||||
// Can be a list of simple types
|
|
||||||
if (!isJavaType(componentType)) { |
|
||||||
processNestedType(componentType, reflectionHints); |
|
||||||
} |
|
||||||
} |
|
||||||
else if (isNestedType(propertyName, propertyClass)) { |
|
||||||
processNestedType(propertyClass, reflectionHints); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private Class<?> getComponentClass(ResolvableType type) { |
|
||||||
ResolvableType componentType = getComponentType(type); |
|
||||||
if (componentType == null) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
if (isContainer(componentType)) { |
|
||||||
// Resolve nested generics like Map<String, List<SomeType>>
|
|
||||||
return getComponentClass(componentType); |
|
||||||
} |
|
||||||
return componentType.toClass(); |
|
||||||
} |
|
||||||
|
|
||||||
private ResolvableType getComponentType(ResolvableType type) { |
|
||||||
if (type.isArray()) { |
|
||||||
return type.getComponentType(); |
|
||||||
} |
|
||||||
if (isCollection(type)) { |
|
||||||
return type.asCollection().getGeneric(); |
|
||||||
} |
|
||||||
if (isMap(type)) { |
|
||||||
return type.asMap().getGeneric(1); |
|
||||||
} |
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
private boolean isContainer(ResolvableType type) { |
|
||||||
return type.isArray() || isCollection(type) || isMap(type); |
|
||||||
} |
|
||||||
|
|
||||||
private boolean isCollection(ResolvableType type) { |
|
||||||
return Collection.class.isAssignableFrom(type.toClass()); |
|
||||||
} |
|
||||||
|
|
||||||
private boolean isMap(ResolvableType type) { |
|
||||||
return Map.class.isAssignableFrom(type.toClass()); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify whether the specified property refer to a nested type. A nested type |
|
||||||
* 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 |
|
||||||
*/ |
|
||||||
private boolean isNestedType(String propertyName, Class<?> propertyType) { |
|
||||||
if (this.type.equals(propertyType.getDeclaringClass())) { |
|
||||||
return true; |
|
||||||
} |
|
||||||
else { |
|
||||||
Field field = ReflectionUtils.findField(this.type, propertyName); |
|
||||||
return field != null && MergedAnnotations.from(field).isPresent(NestedConfigurationProperty.class); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private boolean isJavaType(Class<?> candidate) { |
|
||||||
return candidate.getPackageName().startsWith("java."); |
|
||||||
} |
|
||||||
|
|
||||||
private static BeanInfo getBeanInfo(Class<?> beanType) { |
|
||||||
try { |
|
||||||
BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanType); |
|
||||||
if (beanInfo != null) { |
|
||||||
return beanInfo; |
|
||||||
} |
|
||||||
return Introspector.getBeanInfo(beanType, Introspector.IGNORE_ALL_BEANINFO); |
|
||||||
} |
|
||||||
catch (IntrospectionException ex) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -0,0 +1,270 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2022 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.boot.context.properties.bind; |
||||||
|
|
||||||
|
import java.beans.BeanInfo; |
||||||
|
import java.beans.IntrospectionException; |
||||||
|
import java.beans.Introspector; |
||||||
|
import java.beans.PropertyDescriptor; |
||||||
|
import java.lang.reflect.Constructor; |
||||||
|
import java.lang.reflect.Field; |
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Collection; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.stream.StreamSupport; |
||||||
|
|
||||||
|
import org.springframework.aot.hint.ExecutableMode; |
||||||
|
import org.springframework.aot.hint.ReflectionHints; |
||||||
|
import org.springframework.aot.hint.RuntimeHints; |
||||||
|
import org.springframework.aot.hint.RuntimeHintsRegistrar; |
||||||
|
import org.springframework.beans.BeanInfoFactory; |
||||||
|
import org.springframework.beans.ExtendedBeanInfoFactory; |
||||||
|
import org.springframework.boot.context.properties.NestedConfigurationProperty; |
||||||
|
import org.springframework.core.ResolvableType; |
||||||
|
import org.springframework.core.annotation.MergedAnnotations; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
import org.springframework.util.ReflectionUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* {@link RuntimeHintsRegistrar} that can be used to register {@link ReflectionHints} for |
||||||
|
* {@link Bindable} types, discovering any nested type it may expose via a property. |
||||||
|
* <p> |
||||||
|
* This class can be used as a base-class, or instantiated using the {@code forTypes} |
||||||
|
* factory methods. |
||||||
|
* |
||||||
|
* @author Andy Wilkinson |
||||||
|
* @author Moritz Halbritter |
||||||
|
* @author Sebastien Deleuze |
||||||
|
* @author Phillip Webb |
||||||
|
* @since 3.0.0 |
||||||
|
*/ |
||||||
|
public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { |
||||||
|
|
||||||
|
private static final BeanInfoFactory beanInfoFactory = new ExtendedBeanInfoFactory(); |
||||||
|
|
||||||
|
private final Class<?>[] types; |
||||||
|
|
||||||
|
protected BindableRuntimeHintsRegistrar(Class<?>... types) { |
||||||
|
this.types = types; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void registerHints(RuntimeHints hints, ClassLoader classLoader) { |
||||||
|
registerHints(hints); |
||||||
|
} |
||||||
|
|
||||||
|
public void registerHints(RuntimeHints hints) { |
||||||
|
for (Class<?> type : this.types) { |
||||||
|
new Processor(type).process(hints.reflection()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static BindableRuntimeHintsRegistrar forTypes(Iterable<Class<?>> types) { |
||||||
|
Assert.notNull(types, "Types must not be null"); |
||||||
|
return forTypes(StreamSupport.stream(types.spliterator(), false).toArray(Class<?>[]::new)); |
||||||
|
} |
||||||
|
|
||||||
|
public static BindableRuntimeHintsRegistrar forTypes(Class<?>... types) { |
||||||
|
return new BindableRuntimeHintsRegistrar(types); |
||||||
|
} |
||||||
|
|
||||||
|
private final class Processor { |
||||||
|
|
||||||
|
private final Class<?> type; |
||||||
|
|
||||||
|
private final Constructor<?> bindConstructor; |
||||||
|
|
||||||
|
private final BeanInfo beanInfo; |
||||||
|
|
||||||
|
private final Set<Class<?>> seen; |
||||||
|
|
||||||
|
Processor(Class<?> type) { |
||||||
|
this(type, false, new HashSet<>()); |
||||||
|
} |
||||||
|
|
||||||
|
private Processor(Class<?> type, boolean nestedType, Set<Class<?>> seen) { |
||||||
|
this.type = type; |
||||||
|
this.bindConstructor = BindConstructorProvider.DEFAULT.getBindConstructor(Bindable.of(type), nestedType); |
||||||
|
this.beanInfo = getBeanInfo(type); |
||||||
|
this.seen = seen; |
||||||
|
} |
||||||
|
|
||||||
|
private static BeanInfo getBeanInfo(Class<?> beanType) { |
||||||
|
try { |
||||||
|
BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanType); |
||||||
|
if (beanInfo != null) { |
||||||
|
return beanInfo; |
||||||
|
} |
||||||
|
return Introspector.getBeanInfo(beanType, Introspector.IGNORE_ALL_BEANINFO); |
||||||
|
} |
||||||
|
catch (IntrospectionException ex) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void process(ReflectionHints hints) { |
||||||
|
if (this.seen.contains(this.type)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
this.seen.add(this.type); |
||||||
|
handleConstructor(hints); |
||||||
|
if (this.bindConstructor != null) { |
||||||
|
handleValueObjectProperties(hints); |
||||||
|
} |
||||||
|
else if (this.beanInfo != null) { |
||||||
|
handleJavaBeanProperties(hints); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void handleConstructor(ReflectionHints hints) { |
||||||
|
if (this.bindConstructor != null) { |
||||||
|
hints.registerConstructor(this.bindConstructor, ExecutableMode.INVOKE); |
||||||
|
return; |
||||||
|
} |
||||||
|
Arrays.stream(this.type.getDeclaredConstructors()).filter(this::hasNoParameters).findFirst() |
||||||
|
.ifPresent((constructor) -> hints.registerConstructor(constructor, ExecutableMode.INVOKE)); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean hasNoParameters(Constructor<?> candidate) { |
||||||
|
return candidate.getParameterCount() == 0; |
||||||
|
} |
||||||
|
|
||||||
|
private void handleValueObjectProperties(ReflectionHints hints) { |
||||||
|
for (int i = 0; i < this.bindConstructor.getParameterCount(); i++) { |
||||||
|
String propertyName = this.bindConstructor.getParameters()[i].getName(); |
||||||
|
ResolvableType propertyType = ResolvableType.forConstructorParameter(this.bindConstructor, i); |
||||||
|
handleProperty(hints, propertyName, propertyType); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void handleJavaBeanProperties(ReflectionHints hints) { |
||||||
|
for (PropertyDescriptor propertyDescriptor : this.beanInfo.getPropertyDescriptors()) { |
||||||
|
Method writeMethod = propertyDescriptor.getWriteMethod(); |
||||||
|
if (writeMethod != null) { |
||||||
|
hints.registerMethod(writeMethod, ExecutableMode.INVOKE); |
||||||
|
} |
||||||
|
Method readMethod = propertyDescriptor.getReadMethod(); |
||||||
|
if (readMethod != null) { |
||||||
|
ResolvableType propertyType = ResolvableType.forMethodReturnType(readMethod, this.type); |
||||||
|
String propertyName = propertyDescriptor.getName(); |
||||||
|
if (isSetterMandatory(propertyName, propertyType) && writeMethod == null) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
handleProperty(hints, propertyName, propertyType); |
||||||
|
hints.registerMethod(readMethod, ExecutableMode.INVOKE); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isSetterMandatory(String propertyName, ResolvableType propertyType) { |
||||||
|
Class<?> propertyClass = propertyType.resolve(); |
||||||
|
if (propertyClass == null) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (isContainer(propertyType)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return !isNestedType(propertyName, propertyClass); |
||||||
|
} |
||||||
|
|
||||||
|
private void handleProperty(ReflectionHints hints, String propertyName, ResolvableType propertyType) { |
||||||
|
Class<?> propertyClass = propertyType.resolve(); |
||||||
|
if (propertyClass == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (propertyClass.equals(this.type)) { |
||||||
|
return; // Prevent infinite recursion
|
||||||
|
} |
||||||
|
Class<?> componentType = getComponentClass(propertyType); |
||||||
|
if (componentType != null) { |
||||||
|
// Can be a list of simple types
|
||||||
|
if (!isJavaType(componentType)) { |
||||||
|
processNested(componentType, hints); |
||||||
|
} |
||||||
|
} |
||||||
|
else if (isNestedType(propertyName, propertyClass)) { |
||||||
|
processNested(propertyClass, hints); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void processNested(Class<?> type, ReflectionHints hints) { |
||||||
|
new Processor(type, true, this.seen).process(hints); |
||||||
|
} |
||||||
|
|
||||||
|
private Class<?> getComponentClass(ResolvableType type) { |
||||||
|
ResolvableType componentType = getComponentType(type); |
||||||
|
if (componentType == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
if (isContainer(componentType)) { |
||||||
|
// Resolve nested generics like Map<String, List<SomeType>>
|
||||||
|
return getComponentClass(componentType); |
||||||
|
} |
||||||
|
return componentType.toClass(); |
||||||
|
} |
||||||
|
|
||||||
|
private ResolvableType getComponentType(ResolvableType type) { |
||||||
|
if (type.isArray()) { |
||||||
|
return type.getComponentType(); |
||||||
|
} |
||||||
|
if (isCollection(type)) { |
||||||
|
return type.asCollection().getGeneric(); |
||||||
|
} |
||||||
|
if (isMap(type)) { |
||||||
|
return type.asMap().getGeneric(1); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isContainer(ResolvableType type) { |
||||||
|
return type.isArray() || isCollection(type) || isMap(type); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isCollection(ResolvableType type) { |
||||||
|
return Collection.class.isAssignableFrom(type.toClass()); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isMap(ResolvableType type) { |
||||||
|
return Map.class.isAssignableFrom(type.toClass()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Specify whether the specified property refer to a nested type. A nested type |
||||||
|
* 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 |
||||||
|
*/ |
||||||
|
private boolean isNestedType(String propertyName, Class<?> propertyType) { |
||||||
|
if (this.type.equals(propertyType.getDeclaringClass())) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
Field field = ReflectionUtils.findField(this.type, propertyName); |
||||||
|
return (field != null) && MergedAnnotations.from(field).isPresent(Nested.class); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isJavaType(Class<?> candidate) { |
||||||
|
return candidate.getPackageName().startsWith("java."); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,38 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2022 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.boot.context.properties.bind; |
||||||
|
|
||||||
|
import java.lang.annotation.Documented; |
||||||
|
import java.lang.annotation.ElementType; |
||||||
|
import java.lang.annotation.Retention; |
||||||
|
import java.lang.annotation.RetentionPolicy; |
||||||
|
import java.lang.annotation.Target; |
||||||
|
|
||||||
|
/** |
||||||
|
* Meta-annotation that should be added to annotations that indicate a field is a nested |
||||||
|
* type. Used to ensure that correct reflection hints are registered. |
||||||
|
* |
||||||
|
* @author Phillip Webb |
||||||
|
* @since 3.0.0 |
||||||
|
* @see BindableRuntimeHintsRegistrar |
||||||
|
*/ |
||||||
|
@Retention(RetentionPolicy.RUNTIME) |
||||||
|
@Target(ElementType.ANNOTATION_TYPE) |
||||||
|
@Documented |
||||||
|
public @interface Nested { |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue