BindableRuntimeHintsRegistrar Class — spring-boot Architecture
Architecture documentation for the BindableRuntimeHintsRegistrar class in BindableRuntimeHintsRegistrar.java from the spring-boot codebase.
Entity Profile
Relationship Graph
Source Code
core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java lines 63–353
public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
private static final Log logger = LogFactory.getLog(BindableRuntimeHintsRegistrar.class);
private final Bindable<?>[] bindables;
/**
* Create a new {@link BindableRuntimeHintsRegistrar} for the specified types.
* @param types the types to process
*/
protected BindableRuntimeHintsRegistrar(Class<?>... types) {
this(Stream.of(types).map(Bindable::of).toArray(Bindable[]::new));
}
/**
* Create a new {@link BindableRuntimeHintsRegistrar} for the specified bindables.
* @param bindables the bindables to process
* @since 3.0.8
*/
protected BindableRuntimeHintsRegistrar(Bindable<?>... bindables) {
this.bindables = bindables;
}
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
registerHints(hints);
}
/**
* Contribute hints to the given {@link RuntimeHints} instance.
* @param hints the hints contributed so far for the deployment unit
*/
public void registerHints(RuntimeHints hints) {
for (Bindable<?> bindable : this.bindables) {
try {
new Processor(bindable).process(hints.reflection());
}
catch (Exception ex) {
logger.debug("Skipping hints for " + bindable, ex);
}
}
}
/**
* Create a new {@link BindableRuntimeHintsRegistrar} for the specified types.
* @param types the types to process
* @return a new {@link BindableRuntimeHintsRegistrar} instance
*/
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));
}
/**
* Create a new {@link BindableRuntimeHintsRegistrar} for the specified types.
* @param types the types to process
* @return a new {@link BindableRuntimeHintsRegistrar} instance
*/
public static BindableRuntimeHintsRegistrar forTypes(Class<?>... types) {
return new BindableRuntimeHintsRegistrar(types);
}
/**
* Create a new {@link BindableRuntimeHintsRegistrar} for the specified bindables.
* @param bindables the bindables to process
* @return a new {@link BindableRuntimeHintsRegistrar} instance
* @since 3.0.8
*/
public static BindableRuntimeHintsRegistrar forBindables(Iterable<Bindable<?>> bindables) {
Assert.notNull(bindables, "'bindables' must not be null");
return forBindables(StreamSupport.stream(bindables.spliterator(), false).toArray(Bindable[]::new));
}
/**
* Create a new {@link BindableRuntimeHintsRegistrar} for the specified bindables.
* @param bindables the bindables to process
* @return a new {@link BindableRuntimeHintsRegistrar} instance
* @since 3.0.8
*/
public static BindableRuntimeHintsRegistrar forBindables(Bindable<?>... bindables) {
return new BindableRuntimeHintsRegistrar(bindables);
}
/**
* Processor used to register the hints.
*/
private static final class Processor {
private final Class<?> type;
private final @Nullable Constructor<?> bindConstructor;
private final BeanProperties bean;
private final Set<Class<?>> seen;
Processor(Bindable<?> bindable) {
this(bindable, false, new HashSet<>());
}
private Processor(Bindable<?> bindable, boolean nestedType, Set<Class<?>> seen) {
this.type = getRawClass(bindable);
this.bindConstructor = (bindable.getBindMethod() != BindMethod.JAVA_BEAN)
? BindConstructorProvider.DEFAULT.getBindConstructor(getBindableType(bindable), nestedType) : null;
this.bean = JavaBeanBinder.BeanProperties.of(bindable);
this.seen = seen;
}
private static Class<?> getBindableType(Bindable<?> bindable) {
Class<?> resolved = bindable.getType().resolve();
Assert.state(resolved != null, "'resolved' must not be null");
return resolved;
}
private static Class<?> getRawClass(Bindable<?> bindable) {
Class<?> rawClass = bindable.getType().getRawClass();
Assert.state(rawClass != null, "'rawClass' must not be null");
return rawClass;
}
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.bean != null && !this.bean.getProperties().isEmpty()) {
handleJavaBeanProperties(hints);
}
}
private void handleConstructor(ReflectionHints hints) {
if (this.bindConstructor != null) {
if (KotlinDetector.isKotlinType(this.bindConstructor.getDeclaringClass())) {
KotlinDelegate.handleConstructor(hints, this.bindConstructor);
}
else {
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) {
Assert.state(this.bindConstructor != null, "'bindConstructor' must not be null");
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) {
Map<String, BeanProperty> properties = this.bean.getProperties();
properties.forEach((name, property) -> {
Method getter = property.getGetter();
if (getter != null) {
hints.registerMethod(getter, ExecutableMode.INVOKE);
}
Method setter = property.getSetter();
if (setter != null) {
hints.registerMethod(setter, ExecutableMode.INVOKE);
}
Field field = property.getField();
if (field != null) {
hints.registerField(field);
}
handleProperty(hints, name, property.getType());
});
}
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(Bindable.of(type), true, this.seen).process(hints);
}
private @Nullable 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 @Nullable 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) {
Class<?> declaringClass = propertyType.getDeclaringClass();
if (declaringClass != null && isNested(declaringClass, this.type)) {
return true;
}
Field field = ReflectionUtils.findField(this.type, propertyName);
return (field != null) && MergedAnnotations.from(field).isPresent(Nested.class);
}
private static boolean isNested(Class<?> type, Class<?> candidate) {
if (type.isAssignableFrom(candidate)) {
return true;
}
return (candidate.getDeclaringClass() != null && isNested(type, candidate.getDeclaringClass()));
}
private boolean isJavaType(Class<?> candidate) {
return candidate.getPackageName().startsWith("java.");
}
}
/**
* Inner class to avoid a hard dependency on Kotlin at runtime.
*/
private static final class KotlinDelegate {
static void handleConstructor(ReflectionHints hints, Constructor<?> constructor) {
KClass<?> kClass = JvmClassMappingKt.getKotlinClass(constructor.getDeclaringClass());
if (kClass.isData()) {
hints.registerType(constructor.getDeclaringClass(), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
else {
hints.registerConstructor(constructor, ExecutableMode.INVOKE);
}
}
}
}
Domain
Source
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free