Browse Source

Fix package tangles caused by ConfigurationProperties hints

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-32815
pull/32840/head
Phillip Webb 3 years ago
parent
commit
ea094ddba6
  1. 11
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerRuntimeHints.java
  2. 12
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfiguration.java
  3. 12
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java
  4. 5
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataPropertiesRuntimeHints.java
  5. 6
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java
  6. 252
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessor.java
  7. 5
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/NestedConfigurationProperty.java
  8. 270
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java
  9. 38
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Nested.java

11
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerRuntimeHints.java

@ -16,22 +16,19 @@ @@ -16,22 +16,19 @@
package org.springframework.boot.autoconfigure.freemarker;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider.FreeMarkerTemplateAvailabilityProperties;
import org.springframework.boot.context.properties.ConfigurationPropertiesReflectionHintsProcessor;
import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrar;
/**
* {@link RuntimeHintsRegistrar} for FreeMarker support.
*
* @author Moritz Halbritter
*/
class FreeMarkerRuntimeHints implements RuntimeHintsRegistrar {
class FreeMarkerRuntimeHints extends BindableRuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
ConfigurationPropertiesReflectionHintsProcessor
.processConfigurationProperties(FreeMarkerTemplateAvailabilityProperties.class, hints.reflection());
FreeMarkerRuntimeHints() {
super(FreeMarkerTemplateAvailabilityProperties.class);
}
}

12
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfiguration.java

@ -16,8 +16,6 @@ @@ -16,8 +16,6 @@
package org.springframework.boot.autoconfigure.http;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@ -31,7 +29,7 @@ import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConf @@ -31,7 +29,7 @@ import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConf
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration.NotReactiveWebApplicationCondition;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationPropertiesReflectionHintsProcessor;
import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrar;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.web.servlet.server.Encoding;
import org.springframework.context.annotation.Bean;
@ -102,12 +100,10 @@ public class HttpMessageConvertersAutoConfiguration { @@ -102,12 +100,10 @@ public class HttpMessageConvertersAutoConfiguration {
}
static class HttpMessageConvertersAutoConfigurationRuntimeHints implements RuntimeHintsRegistrar {
static class HttpMessageConvertersAutoConfigurationRuntimeHints extends BindableRuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
ConfigurationPropertiesReflectionHintsProcessor.processConfigurationProperties(Encoding.class,
hints.reflection());
HttpMessageConvertersAutoConfigurationRuntimeHints() {
super(Encoding.class);
}
}

12
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java

@ -36,8 +36,6 @@ import org.apache.commons.logging.Log; @@ -36,8 +36,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aot.AotDetector;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
@ -49,8 +47,8 @@ import org.springframework.beans.factory.support.BeanNameGenerator; @@ -49,8 +47,8 @@ import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.boot.Banner.Mode;
import org.springframework.boot.context.properties.ConfigurationPropertiesReflectionHintsProcessor;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrar;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.convert.ApplicationConversionService;
@ -1428,12 +1426,10 @@ public class SpringApplication { @@ -1428,12 +1426,10 @@ public class SpringApplication {
}
static class SpringApplicationRuntimeHints implements RuntimeHintsRegistrar {
static class SpringApplicationRuntimeHints extends BindableRuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
ConfigurationPropertiesReflectionHintsProcessor.processConfigurationProperties(SpringApplication.class,
hints.reflection());
SpringApplicationRuntimeHints() {
super(SpringApplication.class);
}
}

5
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataPropertiesRuntimeHints.java

@ -19,7 +19,7 @@ package org.springframework.boot.context.config; @@ -19,7 +19,7 @@ package org.springframework.boot.context.config;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.boot.context.properties.ConfigurationPropertiesReflectionHintsProcessor;
import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrar;
import org.springframework.util.ReflectionUtils;
/**
@ -31,8 +31,7 @@ class ConfigDataPropertiesRuntimeHints implements RuntimeHintsRegistrar { @@ -31,8 +31,7 @@ class ConfigDataPropertiesRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
ConfigurationPropertiesReflectionHintsProcessor.processConfigurationProperties(ConfigDataProperties.class,
hints.reflection());
BindableRuntimeHintsRegistrar.forTypes(ConfigDataProperties.class).registerHints(hints);
hints.reflection().registerMethod(ReflectionUtils.findMethod(ConfigDataLocation.class, "of", String.class),
ExecutableMode.INVOKE);
}

6
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java

@ -24,6 +24,7 @@ import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContrib @@ -24,6 +24,7 @@ import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContrib
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrar;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
@ -66,10 +67,7 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessor implements Be @@ -66,10 +67,7 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessor implements Be
@Override
public void applyTo(GenerationContext generationContext,
BeanFactoryInitializationCode beanFactoryInitializationCode) {
for (Class<?> type : this.types) {
ConfigurationPropertiesReflectionHintsProcessor.processConfigurationProperties(type,
generationContext.getRuntimeHints().reflection());
}
BindableRuntimeHintsRegistrar.forTypes(this.types).registerHints(generationContext.getRuntimeHints());
}
}

252
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessor.java

@ -1,252 +0,0 @@ @@ -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;
}
}
}

5
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/NestedConfigurationProperty.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* 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.
@ -22,6 +22,8 @@ import java.lang.annotation.Retention; @@ -22,6 +22,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.context.properties.bind.Nested;
/**
* Indicates that a field in a {@link ConfigurationProperties @ConfigurationProperties}
* object should be treated as if it were a nested type. This annotation has no bearing on
@ -39,6 +41,7 @@ import java.lang.annotation.Target; @@ -39,6 +41,7 @@ import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Nested
public @interface NestedConfigurationProperty {
}

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

@ -0,0 +1,270 @@ @@ -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.");
}
}
}

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

@ -0,0 +1,38 @@ @@ -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…
Cancel
Save