Skip to content

Commit 5ad0d49

Browse files
wilkinsonaphilwebb
andcommitted
Fix hints for @Bean config props that could be constructor bound
Previously, if a `@ConfigurationProperties`-annotated `@Bean` method returned a type that looked like it could be constructor bound, the registered runtime hints were incorrect. With only the bean's class to work with, the hints registrar would incorrectly determine that the type would be constructor bound and would not register the hints required for Java bean binding. This commit updates the registrar to allow the caller to provide a Bindable which knows both what should be bound and how it should be bound, thereby allowing the registrar to generate the correct hints. The tests for the AOT processor have also been updated to remove duplication of the tests in BindableRuntimeHintsRegistrarTests and to focus on the contribution creating Bindable instances with the bind method that is required to produce the correct reflection hints. Closes gh-35564 Co-authored-by: Phillip Webb <[email protected]>
1 parent eb60cf4 commit 5ad0d49

File tree

4 files changed

+235
-46
lines changed

4 files changed

+235
-46
lines changed

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

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,9 +24,10 @@
2424
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
2525
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
2626
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
27+
import org.springframework.boot.context.properties.bind.BindMethod;
28+
import org.springframework.boot.context.properties.bind.Bindable;
2729
import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrar;
2830
import org.springframework.util.ClassUtils;
29-
import org.springframework.util.CollectionUtils;
3031

3132
/**
3233
* {@link BeanFactoryInitializationAotProcessor} that contributes runtime hints for
@@ -43,36 +44,38 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessor implements Be
4344
public ConfigurationPropertiesReflectionHintsContribution processAheadOfTime(
4445
ConfigurableListableBeanFactory beanFactory) {
4546
String[] beanNames = beanFactory.getBeanNamesForAnnotation(ConfigurationProperties.class);
46-
List<Class<?>> types = new ArrayList<>();
47+
List<Bindable<?>> bindables = new ArrayList<>();
4748
for (String beanName : beanNames) {
4849
Class<?> beanType = beanFactory.getType(beanName, false);
4950
if (beanType != null) {
50-
types.add(ClassUtils.getUserClass(beanType));
51+
BindMethod bindMethod = beanFactory.containsBeanDefinition(beanName)
52+
? (BindMethod) beanFactory.getBeanDefinition(beanName).getAttribute(BindMethod.class.getName())
53+
: null;
54+
bindables.add(Bindable.of(ClassUtils.getUserClass(beanType))
55+
.withBindMethod((bindMethod != null) ? bindMethod : BindMethod.JAVA_BEAN));
5156
}
5257
}
53-
if (!CollectionUtils.isEmpty(types)) {
54-
return new ConfigurationPropertiesReflectionHintsContribution(types);
55-
}
56-
return null;
58+
return (!bindables.isEmpty()) ? new ConfigurationPropertiesReflectionHintsContribution(bindables) : null;
5759
}
5860

5961
static final class ConfigurationPropertiesReflectionHintsContribution
6062
implements BeanFactoryInitializationAotContribution {
6163

62-
private final Iterable<Class<?>> types;
64+
private final List<Bindable<?>> bindables;
6365

64-
private ConfigurationPropertiesReflectionHintsContribution(Iterable<Class<?>> types) {
65-
this.types = types;
66+
private ConfigurationPropertiesReflectionHintsContribution(List<Bindable<?>> bindables) {
67+
this.bindables = bindables;
6668
}
6769

6870
@Override
6971
public void applyTo(GenerationContext generationContext,
7072
BeanFactoryInitializationCode beanFactoryInitializationCode) {
71-
BindableRuntimeHintsRegistrar.forTypes(this.types).registerHints(generationContext.getRuntimeHints());
73+
BindableRuntimeHintsRegistrar.forBindables(this.bindables)
74+
.registerHints(generationContext.getRuntimeHints());
7275
}
7376

74-
Iterable<Class<?>> getTypes() {
75-
return this.types;
77+
Iterable<Bindable<?>> getBindables() {
78+
return this.bindables;
7679
}
7780

7881
}

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

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.HashSet;
2525
import java.util.Map;
2626
import java.util.Set;
27+
import java.util.stream.Stream;
2728
import java.util.stream.StreamSupport;
2829

2930
import kotlin.jvm.JvmClassMappingKt;
@@ -51,8 +52,8 @@
5152
* {@link RuntimeHintsRegistrar} that can be used to register {@link ReflectionHints} for
5253
* {@link Bindable} types, discovering any nested type it may expose through a property.
5354
* <p>
54-
* This class can be used as a base-class, or instantiated using the {@code forTypes}
55-
* factory methods.
55+
* This class can be used as a base-class, or instantiated using the {@code forTypes} and
56+
* {@code forBindables} factory methods.
5657
*
5758
* @author Andy Wilkinson
5859
* @author Moritz Halbritter
@@ -62,14 +63,23 @@
6263
*/
6364
public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
6465

65-
private final Class<?>[] types;
66+
private final Bindable<?>[] bindables;
6667

6768
/**
6869
* Create a new {@link BindableRuntimeHintsRegistrar} for the specified types.
6970
* @param types the types to process
7071
*/
7172
protected BindableRuntimeHintsRegistrar(Class<?>... types) {
72-
this.types = types;
73+
this(Stream.of(types).map(Bindable::of).toArray(Bindable[]::new));
74+
}
75+
76+
/**
77+
* Create a new {@link BindableRuntimeHintsRegistrar} for the specified bindables.
78+
* @param bindables the bindables to process
79+
* @since 3.0.8
80+
*/
81+
protected BindableRuntimeHintsRegistrar(Bindable<?>... bindables) {
82+
this.bindables = bindables;
7383
}
7484

7585
@Override
@@ -83,8 +93,8 @@ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
8393
*/
8494
public void registerHints(RuntimeHints hints) {
8595
Set<Class<?>> compiledWithoutParameters = new HashSet<>();
86-
for (Class<?> type : this.types) {
87-
new Processor(type, compiledWithoutParameters).process(hints.reflection());
96+
for (Bindable<?> bindable : this.bindables) {
97+
new Processor(bindable, compiledWithoutParameters).process(hints.reflection());
8898
}
8999
if (!compiledWithoutParameters.isEmpty()) {
90100
throw new MissingParametersCompilerArgumentException(compiledWithoutParameters);
@@ -110,6 +120,27 @@ public static BindableRuntimeHintsRegistrar forTypes(Class<?>... types) {
110120
return new BindableRuntimeHintsRegistrar(types);
111121
}
112122

123+
/**
124+
* Create a new {@link BindableRuntimeHintsRegistrar} for the specified bindables.
125+
* @param bindables the bindables to process
126+
* @return a new {@link BindableRuntimeHintsRegistrar} instance
127+
* @since 3.0.8
128+
*/
129+
public static BindableRuntimeHintsRegistrar forBindables(Iterable<Bindable<?>> bindables) {
130+
Assert.notNull(bindables, "Bindables must not be null");
131+
return forBindables(StreamSupport.stream(bindables.spliterator(), false).toArray(Bindable[]::new));
132+
}
133+
134+
/**
135+
* Create a new {@link BindableRuntimeHintsRegistrar} for the specified bindables.
136+
* @param bindables the bindables to process
137+
* @return a new {@link BindableRuntimeHintsRegistrar} instance
138+
* @since 3.0.8
139+
*/
140+
public static BindableRuntimeHintsRegistrar forBindables(Bindable<?>... bindables) {
141+
return new BindableRuntimeHintsRegistrar(bindables);
142+
}
143+
113144
/**
114145
* Processor used to register the hints.
115146
*/
@@ -136,15 +167,17 @@ private final class Processor {
136167

137168
private final Set<Class<?>> compiledWithoutParameters;
138169

139-
Processor(Class<?> type, Set<Class<?>> compiledWithoutParameters) {
140-
this(type, false, new HashSet<>(), compiledWithoutParameters);
170+
Processor(Bindable<?> bindable, Set<Class<?>> compiledWithoutParameters) {
171+
this(bindable, false, new HashSet<>(), compiledWithoutParameters);
141172
}
142173

143-
private Processor(Class<?> type, boolean nestedType, Set<Class<?>> seen,
174+
private Processor(Bindable<?> bindable, boolean nestedType, Set<Class<?>> seen,
144175
Set<Class<?>> compiledWithoutParameters) {
145-
this.type = type;
146-
this.bindConstructor = BindConstructorProvider.DEFAULT.getBindConstructor(Bindable.of(type), nestedType);
147-
this.bean = JavaBeanBinder.BeanProperties.of(Bindable.of(type));
176+
this.type = bindable.getType().getRawClass();
177+
this.bindConstructor = (bindable.getBindMethod() != BindMethod.JAVA_BEAN)
178+
? BindConstructorProvider.DEFAULT.getBindConstructor(bindable.getType().resolve(), nestedType)
179+
: null;
180+
this.bean = JavaBeanBinder.BeanProperties.of(bindable);
148181
this.seen = seen;
149182
this.compiledWithoutParameters = compiledWithoutParameters;
150183
}
@@ -235,7 +268,7 @@ else if (isNestedType(propertyName, propertyClass)) {
235268
}
236269

237270
private void processNested(Class<?> type, ReflectionHints hints) {
238-
new Processor(type, true, this.seen, this.compiledWithoutParameters).process(hints);
271+
new Processor(Bindable.of(type), true, this.seen, this.compiledWithoutParameters).process(hints);
239272
}
240273

241274
private Class<?> getComponentClass(ResolvableType type) {

0 commit comments

Comments
 (0)