GH-2655: AOT support user declared binders
- Adds support for AOT child context generation for user-declared binder configurations. Fixes #2655
This commit is contained in:
@@ -64,6 +64,11 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure-processor</artifactId>
|
||||
|
||||
@@ -30,6 +30,10 @@ import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationCode;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.cloud.stream.config.BindingServiceConfiguration;
|
||||
import org.springframework.cloud.stream.config.BindingServiceProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
@@ -85,7 +89,13 @@ public class BinderChildContextInitializer implements ApplicationContextAware, B
|
||||
if (registeredBean.getBeanClass().equals(getClass())) { //&& registeredBean.getBeanFactory().equals(this.context)) {
|
||||
this.logger.debug(() -> "Beginning AOT processing for binder child contexts");
|
||||
ensureBinderFactoryIsSet();
|
||||
Map<String, BinderConfiguration> binderConfigurations = this.binderFactory.getBinderConfigurations();
|
||||
// Load the binding service properties from the environment and update the binder factory with them
|
||||
// in order to pick up any user-declared binders. Without this step only the default binder defined
|
||||
// in 'META-INF/spring.binders' will be processed.
|
||||
BindingServiceProperties declaredBinders = this.createBindingServiceProperties();
|
||||
Map<String, BinderConfiguration> binderConfigurations = BindingServiceConfiguration.getBinderConfigurations(
|
||||
this.binderFactory.getBinderTypeRegistry(), declaredBinders);
|
||||
this.binderFactory.updateBinderConfigurations(binderConfigurations);
|
||||
Map<String, ConfigurableApplicationContext> binderChildContexts = binderConfigurations.entrySet().stream()
|
||||
.map(e -> Map.entry(e.getKey(), binderFactory.createBinderContextForAOT(e.getKey())))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
@@ -94,6 +104,13 @@ public class BinderChildContextInitializer implements ApplicationContextAware, B
|
||||
return null;
|
||||
}
|
||||
|
||||
private BindingServiceProperties createBindingServiceProperties() {
|
||||
BindingServiceProperties bindingServiceProperties = new BindingServiceProperties();
|
||||
Binder.get(this.context.getEnvironment())
|
||||
.bind("spring.cloud.stream", Bindable.ofInstance(bindingServiceProperties));
|
||||
return bindingServiceProperties;
|
||||
}
|
||||
|
||||
private void ensureBinderFactoryIsSet() {
|
||||
if (this.binderFactory == null) {
|
||||
Assert.notNull(this.context, () -> "Unable to lookup binder factory from context as this.context is null");
|
||||
@@ -111,7 +128,7 @@ public class BinderChildContextInitializer implements ApplicationContextAware, B
|
||||
@SuppressWarnings({"unused", "raw"})
|
||||
public BinderChildContextInitializer withChildContextInitializers(
|
||||
Map<String, ApplicationContextInitializer<? extends ConfigurableApplicationContext>> childContextInitializers) {
|
||||
this.logger.debug(() -> "Replacing instance w/ one that uses; child context initializers");
|
||||
this.logger.debug(() -> "Replacing instance w/ one that uses child context initializers");
|
||||
Map<String, ApplicationContextInitializer<ConfigurableApplicationContext>> downcastedInitializers =
|
||||
childContextInitializers.entrySet().stream()
|
||||
.map(e -> Map.entry(e.getKey(), (ApplicationContextInitializer<ConfigurableApplicationContext>) e.getValue()))
|
||||
|
||||
@@ -104,6 +104,21 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
|
||||
this.binderCustomizer = binderCustomizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the existing binder configurations - useful in AOT processing where the binding service properties
|
||||
* have to be manually loaded after the binder factory is constructed.
|
||||
*
|
||||
* @param binderConfigurations the updated configurations
|
||||
*/
|
||||
void updateBinderConfigurations(Map<String, BinderConfiguration> binderConfigurations) {
|
||||
this.binderConfigurations.clear();
|
||||
this.binderConfigurations.putAll(binderConfigurations);
|
||||
}
|
||||
|
||||
BinderTypeRegistry getBinderTypeRegistry() {
|
||||
return this.binderTypeRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||
Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext);
|
||||
@@ -126,17 +141,14 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@Override
|
||||
public synchronized <T> Binder<T, ?, ?> getBinder(String name,
|
||||
Class<? extends T> bindingTargetType) {
|
||||
|
||||
public synchronized <T> Binder<T, ?, ?> getBinder(String name, Class<? extends T> bindingTargetType) {
|
||||
String binderName = StringUtils.hasText(name) ? name : this.defaultBinder;
|
||||
|
||||
Map<String, Binder> binders = this.context == null ? Collections.emptyMap()
|
||||
: this.context.getBeansOfType(Binder.class);
|
||||
Map<String, Binder> binders = this.context == null ? Collections.emptyMap() : this.context.getBeansOfType(Binder.class);
|
||||
Binder<T, ConsumerProperties, ProducerProperties> binder;
|
||||
if (StringUtils.hasText(binderName) && binders.containsKey(binderName)) {
|
||||
binder = (Binder<T, ConsumerProperties, ProducerProperties>) this.context
|
||||
.getBean(binderName);
|
||||
binder = (Binder<T, ConsumerProperties, ProducerProperties>) this.context.getBean(binderName);
|
||||
}
|
||||
else if (binders.size() == 1) {
|
||||
binder = binders.values().iterator().next();
|
||||
@@ -149,7 +161,7 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* This is the fall back to the old bootstrap that relies on spring.binders.
|
||||
* This is the fallback to the old bootstrap that relies on spring.binders.
|
||||
*/
|
||||
binder = this.doGetBinder(binderName, bindingTargetType);
|
||||
}
|
||||
@@ -160,27 +172,37 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
|
||||
}
|
||||
|
||||
private <T> Binder<T, ConsumerProperties, ProducerProperties> doGetBinder(String name, Class<? extends T> bindingTargetType) {
|
||||
if (CollectionUtils.isEmpty(this.binderChildContextInitializers)) {
|
||||
return this.doGetBinderConventional(name, bindingTargetType);
|
||||
// If child initializers - use AOT lookup
|
||||
if (!CollectionUtils.isEmpty(this.binderChildContextInitializers)) {
|
||||
return this.doGetBinderAOT(name, bindingTargetType);
|
||||
}
|
||||
else {
|
||||
if ((!StringUtils.hasText(name) || this.defaultBinder != null) && this.binderChildContextInitializers.size() == 1) {
|
||||
return this.doGetBinderConventional(name, bindingTargetType);
|
||||
}
|
||||
|
||||
private <T> Binder<T, ConsumerProperties, ProducerProperties> doGetBinderAOT(String name, Class<? extends T> bindingTargetType) {
|
||||
// If neither name nor default given - return single or fail when > 1
|
||||
if (!StringUtils.hasText(name) && !StringUtils.hasText(this.defaultBinder)) {
|
||||
if (this.binderChildContextInitializers.size() == 1) {
|
||||
String configurationName = this.binderChildContextInitializers.keySet().iterator().next();
|
||||
this.logger.debug("No specific name or default given - using single available child initializer '" + configurationName + "'");
|
||||
return this.getBinderInstance(configurationName);
|
||||
}
|
||||
else if (this.defaultBinder != null && this.binderChildContextInitializers.size() > 1) {
|
||||
// Handling default binder when different binders are present on the classpath.
|
||||
for (String binderName : this.binderChildContextInitializers.keySet()) {
|
||||
if (binderName.equals(this.defaultBinder)) {
|
||||
return this.getBinderInstance(binderName);
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Default binder provided, but can't determine which binder to initialize");
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("Can't determine which binder to use: " + name + "/" + this.binderChildContextInitializers.size());
|
||||
}
|
||||
throw new IllegalStateException("No specific name or default given - can't determine which binder to use");
|
||||
}
|
||||
|
||||
// Prefer specific name over default
|
||||
String configurationName = name;
|
||||
if (!StringUtils.hasText(configurationName)) {
|
||||
configurationName = this.defaultBinder;
|
||||
}
|
||||
|
||||
// Check for matching child initializer
|
||||
if (this.binderChildContextInitializers.containsKey(configurationName)) {
|
||||
return this.getBinderInstance(configurationName);
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Requested binder '" + name + "' did not match available binders: " +
|
||||
this.binderChildContextInitializers.keySet());
|
||||
}
|
||||
|
||||
private <T> Binder<T, ConsumerProperties, ProducerProperties> doGetBinderConventional(String name,
|
||||
@@ -296,16 +318,19 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
|
||||
ConfigurableApplicationContext binderProducingContext;
|
||||
if (this.binderChildContextInitializers.containsKey(configurationName)) {
|
||||
this.logger.info("Using AOT pre-prepared initializer to construct binder child context for " + configurationName);
|
||||
if (binderConfiguration != null) {
|
||||
this.flatten(null, binderConfiguration.getProperties(), binderProperties);
|
||||
}
|
||||
binderProducingContext = this.createUnitializedContextForAOT(configurationName, binderProperties, binderConfiguration);
|
||||
this.binderChildContextInitializers.get(configurationName).initialize(binderProducingContext);
|
||||
binderProducingContext.refresh();
|
||||
}
|
||||
else {
|
||||
this.logger.info("Constructing binder child context for " + configurationName);
|
||||
Assert.state(binderConfiguration != null, "Unknown binder configuration: " + configurationName);
|
||||
this.flatten(null, binderConfiguration.getProperties(), binderProperties);
|
||||
BinderType binderType = this.binderTypeRegistry.get(binderConfiguration.getBinderType());
|
||||
Assert.notNull(binderType, "Binder type " + binderConfiguration.getBinderType() + " is not defined");
|
||||
this.flatten(null, binderConfiguration.getProperties(), binderProperties);
|
||||
this.logger.info("Constructing binder child context for " + configurationName);
|
||||
binderProducingContext = this.initializeBinderContextSimple(configurationName, binderProperties,
|
||||
binderType, binderConfiguration, true);
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ public class BindingServiceConfiguration {
|
||||
@Autowired(required = false)
|
||||
private Collection<DefaultBinderFactory.Listener> binderFactoryListeners;
|
||||
|
||||
private static Map<String, BinderConfiguration> getBinderConfigurations(
|
||||
public static Map<String, BinderConfiguration> getBinderConfigurations(
|
||||
BinderTypeRegistry binderTypeRegistry,
|
||||
BindingServiceProperties bindingServiceProperties) {
|
||||
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright 2022-2023 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.cloud.stream.binder;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.aot.AotDetector;
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.context.annotation.UserConfigurations;
|
||||
import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.system.CapturedOutput;
|
||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.cloud.stream.config.BinderFactoryAutoConfiguration;
|
||||
import org.springframework.cloud.stream.config.BindingServiceConfiguration;
|
||||
import org.springframework.cloud.stream.function.FunctionConfiguration;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.aot.ApplicationContextAotGenerator;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
|
||||
import org.springframework.core.test.tools.TestCompiler;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for the {@link BinderChildContextInitializer}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class BinderChildContextInitializerTests {
|
||||
|
||||
private static final LogAccessor LOG = new LogAccessor(BinderChildContextInitializerTests.class);
|
||||
|
||||
@Test
|
||||
@CompileWithForkedClassLoader
|
||||
void shouldStartDefaultBinderChildContextFromAotContributions(CapturedOutput output) {
|
||||
|
||||
// Test description:
|
||||
// -----------------------
|
||||
// Use context runner to create a boostrap context that we can then pass into AOT processor.
|
||||
// The AOT processor will then generate the ACI for the default binder (no user declared binders).
|
||||
// We then initialize a fresh app context using the generated ACI and verify the expected output.
|
||||
|
||||
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(BinderFactoryAutoConfiguration.class,
|
||||
BindingServiceConfiguration.class, FunctionConfiguration.class))
|
||||
.withInitializer(new ConfigDataApplicationContextInitializer())
|
||||
.withConfiguration(UserConfigurations.of(TestFooBinderAppConfiguration.class));
|
||||
contextRunner.prepare(context -> {
|
||||
TestGenerationContext generationContext = new TestGenerationContext(TestTarget.class);
|
||||
ClassName className = new ApplicationContextAotGenerator().processAheadOfTime(
|
||||
(GenericApplicationContext) context.getSourceApplicationContext(), generationContext);
|
||||
generationContext.writeGeneratedContent();
|
||||
TestCompiler compiler = TestCompiler.forSystem();
|
||||
compiler.with(generationContext).compile(compiled -> {
|
||||
// Initialize the context w/ the generated ACI
|
||||
GenericApplicationContext freshApplicationContext = new GenericApplicationContext();
|
||||
ApplicationContextInitializer<GenericApplicationContext> initializer = compiled
|
||||
.getInstance(ApplicationContextInitializer.class, className.toString());
|
||||
initializer.initialize(freshApplicationContext);
|
||||
assertThat(output).contains("Beginning AOT processing for binder child contexts");
|
||||
assertThat(output).contains("Pre-creating binder child context (AOT) for mock");
|
||||
assertThat(output).contains("Generating AOT child context initializer for mock");
|
||||
assertThat(output).contains("Refreshing mock_context");
|
||||
|
||||
// Refresh the initialized context and verify the binder child contexts are used
|
||||
TestPropertyValues.of(AotDetector.AOT_ENABLED + "=true")
|
||||
.applyToSystemProperties(freshApplicationContext::refresh);
|
||||
assertThat(output).contains("Replacing instance w/ one that uses child context initializers");
|
||||
assertThat(output).contains("Setting binder child context initializers on binder factory");
|
||||
|
||||
// Make sure we can get the binders
|
||||
DefaultBinderFactory binderFactory = freshApplicationContext.getBean(DefaultBinderFactory.class);
|
||||
Binder<MessageChannel, ?, ?> mockBinder = binderFactory.getBinder("mock", MessageChannel.class);
|
||||
assertThat(mockBinder).isNotNull();
|
||||
assertThat(output).contains("Caching the binder: mock");
|
||||
|
||||
// no default or name given - uses single available binder
|
||||
assertThat(binderFactory.getBinder(null, MessageChannel.class)).isSameAs(mockBinder);
|
||||
assertThat(output).contains("No specific name or default given - using single available child initializer 'mock'");
|
||||
|
||||
assertThatIllegalStateException().isThrownBy(
|
||||
() -> binderFactory.getBinder("mockBinder1", MessageChannel.class))
|
||||
.withMessageContaining("Requested binder 'mockBinder1' did not match available binders");
|
||||
|
||||
binderFactory.setDefaultBinder("mock");
|
||||
assertThat(binderFactory.getBinder(null, MessageChannel.class)).isSameAs(mockBinder);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@CompileWithForkedClassLoader
|
||||
@SuppressWarnings("unchecked")
|
||||
void shouldStartDeclardBinderChildContextsFromAotContributions(CapturedOutput output) {
|
||||
|
||||
// Test description:
|
||||
// -----------------------
|
||||
// Use context runner to create a boostrap context that we can then pass into AOT processor.
|
||||
// The AOT processor will then generate the ACI for each binder child context defined in the application.yml.
|
||||
// We then initialize a fresh app context using the generated ACIs and verify the expected output.
|
||||
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(BinderFactoryAutoConfiguration.class,
|
||||
BindingServiceConfiguration.class, FunctionConfiguration.class))
|
||||
.withInitializer(new ConfigDataApplicationContextInitializer())
|
||||
.withPropertyValues("spring.config.location=classpath:binder-aot-test/")
|
||||
.withConfiguration(UserConfigurations.of(TestFooBinderAppConfiguration.class));
|
||||
|
||||
contextRunner.prepare(context -> {
|
||||
TestGenerationContext generationContext = new TestGenerationContext(TestTarget.class);
|
||||
ClassName className = new ApplicationContextAotGenerator().processAheadOfTime(
|
||||
(GenericApplicationContext) context.getSourceApplicationContext(), generationContext);
|
||||
generationContext.writeGeneratedContent();
|
||||
TestCompiler compiler = TestCompiler.forSystem();
|
||||
compiler.with(generationContext).compile(compiled -> {
|
||||
// Initialize the context w/ the generated ACI
|
||||
GenericApplicationContext freshApplicationContext = new GenericApplicationContext();
|
||||
ApplicationContextInitializer<GenericApplicationContext> initializer = compiled
|
||||
.getInstance(ApplicationContextInitializer.class, className.toString());
|
||||
initializer.initialize(freshApplicationContext);
|
||||
|
||||
assertThat(output).contains("Beginning AOT processing for binder child contexts");
|
||||
assertThat(output).contains("Pre-creating binder child context (AOT) for mockBinder2");
|
||||
assertThat(output).contains("Pre-creating binder child context (AOT) for mockBinder1");
|
||||
assertThat(output).contains("Generating AOT child context initializer for mockBinder2");
|
||||
assertThat(output).contains("Refreshing mockBinder2_context");
|
||||
assertThat(output).contains("Generating AOT child context initializer for mockBinder1");
|
||||
assertThat(output).contains("Refreshing mockBinder1_context");
|
||||
|
||||
// Refresh the initialized context and verify the binder child contexts are used
|
||||
TestPropertyValues.of(AotDetector.AOT_ENABLED + "=true")
|
||||
.applyToSystemProperties(freshApplicationContext::refresh);
|
||||
|
||||
assertThat(output).contains("Replacing instance w/ one that uses child context initializers");
|
||||
assertThat(output).contains("Setting binder child context initializers on binder factory");
|
||||
|
||||
// Make sure we can get the binders
|
||||
DefaultBinderFactory binderFactory = freshApplicationContext.getBean(DefaultBinderFactory.class);
|
||||
assertThat(binderFactory.getBinder("mockBinder1", MessageChannel.class)).isNotNull();
|
||||
assertThat(output).contains("Caching the binder: mockBinder1");
|
||||
|
||||
Binder mockBinder2 = binderFactory.getBinder("mockBinder2", MessageChannel.class);
|
||||
assertThat(mockBinder2).isNotNull();
|
||||
assertThat(output).contains("Caching the binder: mockBinder2");
|
||||
|
||||
assertThatIllegalStateException().isThrownBy(
|
||||
() -> binderFactory.getBinder("mockBinder3", MessageChannel.class))
|
||||
.withMessageContaining("Requested binder 'mockBinder3' did not match available binders");
|
||||
|
||||
assertThatIllegalStateException().isThrownBy(
|
||||
() -> binderFactory.getBinder(null, MessageChannel.class))
|
||||
.withMessageContaining("No specific name or default given - can't determine which binder to use");
|
||||
|
||||
binderFactory.setDefaultBinder("mockBinder2");
|
||||
assertThat(binderFactory.getBinder(null, MessageChannel.class)).isSameAs(mockBinder2);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static class TestTarget {
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestFooBinderAppConfiguration {
|
||||
|
||||
@Bean
|
||||
GenericConversionService integrationConversionService() {
|
||||
return mock(GenericConversionService.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
Supplier<String> fooSource() {
|
||||
return () -> "foo-" + System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Consumer<String> fooSink() {
|
||||
return (foo) -> LOG.info("*** FOO: " + foo);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2023-2023 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.cloud.stream.binder;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultBinderFactory}
|
||||
*
|
||||
* @author Chris Bono
|
||||
*/
|
||||
class DefaultBinderFactoryTests {
|
||||
|
||||
@Test
|
||||
void updateBinderConfigurations() {
|
||||
Map<String, BinderConfiguration> binderConfigs = new HashMap<>();
|
||||
binderConfigs.put("foo", mock(BinderConfiguration.class));
|
||||
DefaultBinderFactory binderFactory = new DefaultBinderFactory(binderConfigs, null, null);
|
||||
|
||||
Map<String, BinderConfiguration> newBinderConfigs = new HashMap<>();
|
||||
newBinderConfigs.put("bar", mock(BinderConfiguration.class));
|
||||
binderFactory.updateBinderConfigurations(newBinderConfigs);
|
||||
|
||||
assertThat(binderFactory.getBinderConfigurations()).containsExactlyInAnyOrderEntriesOf(newBinderConfigs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
spring.cloud:
|
||||
function:
|
||||
definition: fooSource;fooSink
|
||||
stream:
|
||||
default-binder: mockBinder1
|
||||
binders:
|
||||
mockBinder1:
|
||||
type: mock
|
||||
environment:
|
||||
foo: bar1
|
||||
mockBinder2:
|
||||
type: mock
|
||||
environment:
|
||||
foo: bar2
|
||||
bindings:
|
||||
fooSource-out-0:
|
||||
destination: fooSink-in-0
|
||||
binder: mockBinder2
|
||||
fooSink-in-0:
|
||||
destination: fooSource-out-0
|
||||
binder: mockBinder2
|
||||
Reference in New Issue
Block a user