Skip to content

Commit 54ccf5d

Browse files
committed
spring-cloudGH-2655: AOT support user declared binders
- Adds support for AOT child context generation for user-declared binder configurations.
1 parent 64835ef commit 54ccf5d

File tree

7 files changed

+356
-28
lines changed

7 files changed

+356
-28
lines changed

core/spring-cloud-stream/pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@
6464
<artifactId>spring-boot-starter-test</artifactId>
6565
<scope>test</scope>
6666
</dependency>
67+
<dependency>
68+
<groupId>org.springframework</groupId>
69+
<artifactId>spring-core-test</artifactId>
70+
<scope>test</scope>
71+
</dependency>
6772
<dependency>
6873
<groupId>org.springframework.boot</groupId>
6974
<artifactId>spring-boot-autoconfigure-processor</artifactId>

core/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderChildContextInitializer.java

+19-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
3131
import org.springframework.beans.factory.aot.BeanRegistrationCode;
3232
import org.springframework.beans.factory.support.RegisteredBean;
33+
import org.springframework.boot.context.properties.bind.Bindable;
34+
import org.springframework.boot.context.properties.bind.Binder;
35+
import org.springframework.cloud.stream.config.BindingServiceConfiguration;
36+
import org.springframework.cloud.stream.config.BindingServiceProperties;
3337
import org.springframework.context.ApplicationContext;
3438
import org.springframework.context.ApplicationContextAware;
3539
import org.springframework.context.ApplicationContextInitializer;
@@ -85,7 +89,13 @@ public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registe
8589
if (registeredBean.getBeanClass().equals(getClass())) { //&& registeredBean.getBeanFactory().equals(this.context)) {
8690
this.logger.debug(() -> "Beginning AOT processing for binder child contexts");
8791
ensureBinderFactoryIsSet();
88-
Map<String, BinderConfiguration> binderConfigurations = this.binderFactory.getBinderConfigurations();
92+
// Load the binding service properties from the environment and update the binder factory with them
93+
// in order to pick up any user-declared binders. Without this step only the default binder defined
94+
// in 'META-INF/spring.binders' will be processed.
95+
BindingServiceProperties declaredBinders = this.createBindingServiceProperties();
96+
Map<String, BinderConfiguration> binderConfigurations = BindingServiceConfiguration.getBinderConfigurations(
97+
this.binderFactory.getBinderTypeRegistry(), declaredBinders);
98+
this.binderFactory.updateBinderConfigurations(binderConfigurations);
8999
Map<String, ConfigurableApplicationContext> binderChildContexts = binderConfigurations.entrySet().stream()
90100
.map(e -> Map.entry(e.getKey(), binderFactory.createBinderContextForAOT(e.getKey())))
91101
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
@@ -94,6 +104,13 @@ public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registe
94104
return null;
95105
}
96106

107+
private BindingServiceProperties createBindingServiceProperties() {
108+
BindingServiceProperties bindingServiceProperties = new BindingServiceProperties();
109+
Binder.get(this.context.getEnvironment())
110+
.bind("spring.cloud.stream", Bindable.ofInstance(bindingServiceProperties));
111+
return bindingServiceProperties;
112+
}
113+
97114
private void ensureBinderFactoryIsSet() {
98115
if (this.binderFactory == null) {
99116
Assert.notNull(this.context, () -> "Unable to lookup binder factory from context as this.context is null");
@@ -111,7 +128,7 @@ private void ensureBinderFactoryIsSet() {
111128
@SuppressWarnings({"unused", "raw"})
112129
public BinderChildContextInitializer withChildContextInitializers(
113130
Map<String, ApplicationContextInitializer<? extends ConfigurableApplicationContext>> childContextInitializers) {
114-
this.logger.debug(() -> "Replacing instance w/ one that uses; child context initializers");
131+
this.logger.debug(() -> "Replacing instance w/ one that uses child context initializers");
115132
Map<String, ApplicationContextInitializer<ConfigurableApplicationContext>> downcastedInitializers =
116133
childContextInitializers.entrySet().stream()
117134
.map(e -> Map.entry(e.getKey(), (ApplicationContextInitializer<ConfigurableApplicationContext>) e.getValue()))

core/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/DefaultBinderFactory.java

+50-25
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,21 @@ public DefaultBinderFactory(Map<String, BinderConfiguration> binderConfiguration
104104
this.binderCustomizer = binderCustomizer;
105105
}
106106

107+
/**
108+
* Replaces the existing binder configurations - useful in AOT processing where the binding service properties
109+
* have to be manually loaded after the binder factory is constructed.
110+
*
111+
* @param binderConfigurations the updated configurations
112+
*/
113+
void updateBinderConfigurations(Map<String, BinderConfiguration> binderConfigurations) {
114+
this.binderConfigurations.clear();
115+
this.binderConfigurations.putAll(binderConfigurations);
116+
}
117+
118+
BinderTypeRegistry getBinderTypeRegistry() {
119+
return this.binderTypeRegistry;
120+
}
121+
107122
@Override
108123
public void setApplicationContext(ApplicationContext applicationContext) {
109124
Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext);
@@ -126,17 +141,14 @@ public void destroy() {
126141

127142
@SuppressWarnings({ "unchecked", "rawtypes" })
128143
@Override
129-
public synchronized <T> Binder<T, ?, ?> getBinder(String name,
130-
Class<? extends T> bindingTargetType) {
131144

145+
public synchronized <T> Binder<T, ?, ?> getBinder(String name, Class<? extends T> bindingTargetType) {
132146
String binderName = StringUtils.hasText(name) ? name : this.defaultBinder;
133147

134-
Map<String, Binder> binders = this.context == null ? Collections.emptyMap()
135-
: this.context.getBeansOfType(Binder.class);
148+
Map<String, Binder> binders = this.context == null ? Collections.emptyMap() : this.context.getBeansOfType(Binder.class);
136149
Binder<T, ConsumerProperties, ProducerProperties> binder;
137150
if (StringUtils.hasText(binderName) && binders.containsKey(binderName)) {
138-
binder = (Binder<T, ConsumerProperties, ProducerProperties>) this.context
139-
.getBean(binderName);
151+
binder = (Binder<T, ConsumerProperties, ProducerProperties>) this.context.getBean(binderName);
140152
}
141153
else if (binders.size() == 1) {
142154
binder = binders.values().iterator().next();
@@ -149,7 +161,7 @@ else if (binders.size() > 1) {
149161
}
150162
else {
151163
/*
152-
* This is the fall back to the old bootstrap that relies on spring.binders.
164+
* This is the fallback to the old bootstrap that relies on spring.binders.
153165
*/
154166
binder = this.doGetBinder(binderName, bindingTargetType);
155167
}
@@ -160,27 +172,37 @@ else if (binders.size() > 1) {
160172
}
161173

162174
private <T> Binder<T, ConsumerProperties, ProducerProperties> doGetBinder(String name, Class<? extends T> bindingTargetType) {
163-
if (CollectionUtils.isEmpty(this.binderChildContextInitializers)) {
164-
return this.doGetBinderConventional(name, bindingTargetType);
175+
// If child initializers - use AOT lookup
176+
if (!CollectionUtils.isEmpty(this.binderChildContextInitializers)) {
177+
return this.doGetBinderAOT(name, bindingTargetType);
165178
}
166-
else {
167-
if ((!StringUtils.hasText(name) || this.defaultBinder != null) && this.binderChildContextInitializers.size() == 1) {
179+
return this.doGetBinderConventional(name, bindingTargetType);
180+
}
181+
182+
private <T> Binder<T, ConsumerProperties, ProducerProperties> doGetBinderAOT(String name, Class<? extends T> bindingTargetType) {
183+
// If neither name nor default given - return single or fail when > 1
184+
if (!StringUtils.hasText(name) && !StringUtils.hasText(this.defaultBinder)) {
185+
if (this.binderChildContextInitializers.size() == 1) {
168186
String configurationName = this.binderChildContextInitializers.keySet().iterator().next();
187+
this.logger.debug("No specific name or default given - using single available child initializer '" + configurationName + "'");
169188
return this.getBinderInstance(configurationName);
170189
}
171-
else if (this.defaultBinder != null && this.binderChildContextInitializers.size() > 1) {
172-
// Handling default binder when different binders are present on the classpath.
173-
for (String binderName : this.binderChildContextInitializers.keySet()) {
174-
if (binderName.equals(this.defaultBinder)) {
175-
return this.getBinderInstance(binderName);
176-
}
177-
}
178-
throw new IllegalStateException("Default binder provided, but can't determine which binder to initialize");
179-
}
180-
else {
181-
throw new IllegalStateException("Can't determine which binder to use: " + name + "/" + this.binderChildContextInitializers.size());
182-
}
190+
throw new IllegalStateException("No specific name or default given - can't determine which binder to use");
191+
}
192+
193+
// Prefer specific name over default
194+
String configurationName = name;
195+
if (!StringUtils.hasText(configurationName)) {
196+
configurationName = this.defaultBinder;
197+
}
198+
199+
// Check for matching child initializer
200+
if (this.binderChildContextInitializers.containsKey(configurationName)) {
201+
return this.getBinderInstance(configurationName);
183202
}
203+
204+
throw new IllegalStateException("Requested binder '" + name + "' did not match available binders: " +
205+
this.binderChildContextInitializers.keySet());
184206
}
185207

186208
private <T> Binder<T, ConsumerProperties, ProducerProperties> doGetBinderConventional(String name,
@@ -296,16 +318,19 @@ private <T> Binder<T, ConsumerProperties, ProducerProperties> getBinderInstance(
296318
ConfigurableApplicationContext binderProducingContext;
297319
if (this.binderChildContextInitializers.containsKey(configurationName)) {
298320
this.logger.info("Using AOT pre-prepared initializer to construct binder child context for " + configurationName);
321+
if (binderConfiguration != null) {
322+
this.flatten(null, binderConfiguration.getProperties(), binderProperties);
323+
}
299324
binderProducingContext = this.createUnitializedContextForAOT(configurationName, binderProperties, binderConfiguration);
300325
this.binderChildContextInitializers.get(configurationName).initialize(binderProducingContext);
301326
binderProducingContext.refresh();
302327
}
303328
else {
329+
this.logger.info("Constructing binder child context for " + configurationName);
304330
Assert.state(binderConfiguration != null, "Unknown binder configuration: " + configurationName);
331+
this.flatten(null, binderConfiguration.getProperties(), binderProperties);
305332
BinderType binderType = this.binderTypeRegistry.get(binderConfiguration.getBinderType());
306333
Assert.notNull(binderType, "Binder type " + binderConfiguration.getBinderType() + " is not defined");
307-
this.flatten(null, binderConfiguration.getProperties(), binderProperties);
308-
this.logger.info("Constructing binder child context for " + configurationName);
309334
binderProducingContext = this.initializeBinderContextSimple(configurationName, binderProperties,
310335
binderType, binderConfiguration, true);
311336
}

core/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public class BindingServiceConfiguration {
9090
@Autowired(required = false)
9191
private Collection<DefaultBinderFactory.Listener> binderFactoryListeners;
9292

93-
private static Map<String, BinderConfiguration> getBinderConfigurations(
93+
public static Map<String, BinderConfiguration> getBinderConfigurations(
9494
BinderTypeRegistry binderTypeRegistry,
9595
BindingServiceProperties bindingServiceProperties) {
9696

0 commit comments

Comments
 (0)