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
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
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 {
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
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 {
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
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;
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Nested
|
||||
public @interface NestedConfigurationProperty {
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user