Polish "Add auto-configuration for spring-rabbit-stream"

See gh-27480
This commit is contained in:
Andy Wilkinson 2021-08-11 13:08:44 +01:00
parent 9784838229
commit 7a0fe0f95f
3 changed files with 162 additions and 47 deletions

View File

@ -897,8 +897,8 @@ public class RabbitProperties {
public static class StreamContainer extends BaseContainer {
/**
* When true, the container factory will create containers that support listeners
* that consume native stream messages instead of spring-amqp {@code Message}s.
* Whether the container will support listeners that consume native stream
* messages instead of Spring AMQP messages.
*/
boolean nativeListener;
@ -1172,24 +1172,24 @@ public class RabbitProperties {
public static final class Stream {
/**
* Host of a RabbitMQ instance with the Stream Plugin Enabled
* Host of a RabbitMQ instance with the Stream plugin enabled.
*/
private String host = "localhost";
/**
* Stream port of a RabbitMQ instance with the Stream Plugin Enabled
* Stream port of a RabbitMQ instance with the Stream plugin enabled.
*/
private int port = DEFAULT_STREAM_PORT;
/**
* Login user to authenticate to the broker. If not set
* {@code spring.rabbitmq.username} will be used.
* Login user to authenticate to the broker. When not set,
* spring.rabbitmq.username is used.
*/
private String username;
/**
* Login password to authenticate to the broker. If not set
* {@code spring.rabbitmq.password} will be used.
* Login password to authenticate to the broker. When not set
* spring.rabbitmq.password is used.
*/
private String password;

View File

@ -16,15 +16,18 @@
package org.springframework.boot.autoconfigure.amqp;
import java.util.function.Function;
import java.util.function.Supplier;
import com.rabbitmq.stream.Environment;
import com.rabbitmq.stream.EnvironmentBuilder;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.config.ContainerCustomizer;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory;
@ -32,7 +35,7 @@ import org.springframework.rabbit.stream.listener.ConsumerCustomizer;
import org.springframework.rabbit.stream.listener.StreamListenerContainer;
/**
* Configuration for Spring RabbitMQ Stream Plugin support.
* Configuration for Spring RabbitMQ Stream plugin support.
*
* @author Gary Russell
*/
@ -46,7 +49,6 @@ class RabbitStreamConfiguration {
StreamRabbitListenerContainerFactory streamRabbitListenerContainerFactory(Environment rabbitStreamEnvironment,
RabbitProperties properties, ObjectProvider<ConsumerCustomizer> consumerCustomizer,
ObjectProvider<ContainerCustomizer<StreamListenerContainer>> containerCustomizer) {
StreamRabbitListenerContainerFactory factory = new StreamRabbitListenerContainerFactory(
rabbitStreamEnvironment);
factory.setNativeListener(properties.getListener().getStream().isNativeListener());
@ -58,24 +60,22 @@ class RabbitStreamConfiguration {
@Bean(name = "rabbitStreamEnvironment")
@ConditionalOnMissingBean(name = "rabbitStreamEnvironment")
Environment rabbitStreamEnvironment(RabbitProperties properties) {
return configure(Environment.builder(), properties).build();
}
static EnvironmentBuilder configure(EnvironmentBuilder builder, RabbitProperties properties) {
builder.lazyInitialization(true);
RabbitProperties.Stream stream = properties.getStream();
String username = stream.getUsername();
if (username == null) {
username = properties.getUsername();
PropertyMapper mapper = PropertyMapper.get();
mapper.from(stream.getHost()).to(builder::host);
mapper.from(stream.getPort()).to(builder::port);
mapper.from(stream.getUsername()).as(withFallback(properties::getUsername)).whenNonNull().to(builder::username);
mapper.from(stream.getPassword()).as(withFallback(properties::getPassword)).whenNonNull().to(builder::password);
return builder;
}
String password = stream.getPassword();
if (password == null) {
password = properties.getPassword();
}
EnvironmentBuilder builder = Environment.builder().lazyInitialization(true).host(stream.getHost())
.port(stream.getPort());
if (username != null) {
builder.username(username);
}
if (password != null) {
builder.password(password);
}
return builder.build();
private static Function<String, String> withFallback(Supplier<String> fallback) {
return (value) -> (value != null) ? value : fallback.get();
}
}

View File

@ -16,12 +16,16 @@
package org.springframework.boot.autoconfigure.amqp;
import com.rabbitmq.stream.Environment;
import com.rabbitmq.stream.EnvironmentBuilder;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.config.ContainerCustomizer;
import org.springframework.amqp.rabbit.listener.MessageListenerContainer;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@ -31,11 +35,15 @@ import org.springframework.rabbit.stream.listener.ConsumerCustomizer;
import org.springframework.rabbit.stream.listener.StreamListenerContainer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests for {@link RabbitStreamConfiguration}.
*
* @author Gary Russell
* @author Andy Wilkinson
*/
class RabbitStreamConfigurationTests {
@ -43,26 +51,106 @@ class RabbitStreamConfigurationTests {
.withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class));
@Test
void testContainerType() {
@SuppressWarnings("unchecked")
void whenListenerTypeIsStreamThenStreamListenerContainerAndEnvironmentAreAutoConfigured() {
this.contextRunner.withUserConfiguration(TestConfiguration.class)
.withPropertyValues("spring.rabbitmq.listener.type:stream",
"spring.rabbitmq.listener.stream.native-listener:true")
.run((context) -> {
.withPropertyValues("spring.rabbitmq.listener.type:stream").run((context) -> {
RabbitListenerEndpointRegistry registry = context.getBean(RabbitListenerEndpointRegistry.class);
assertThat(registry.getListenerContainer("test")).isInstanceOf(StreamListenerContainer.class);
assertThat(new DirectFieldAccessor(registry.getListenerContainer("test"))
.getPropertyValue("consumerCustomizer")).isNotNull();
assertThat(new DirectFieldAccessor(context.getBean(StreamRabbitListenerContainerFactory.class))
.getPropertyValue("nativeListener")).isEqualTo(Boolean.TRUE);
assertThat(context.getBean(TestConfiguration.class).containerCustomizerCalled).isTrue();
MessageListenerContainer listenerContainer = registry.getListenerContainer("test");
assertThat(listenerContainer).isInstanceOf(StreamListenerContainer.class);
assertThat(listenerContainer).extracting("consumerCustomizer").isNotNull();
assertThat(context.getBean(StreamRabbitListenerContainerFactory.class))
.extracting("nativeListener", InstanceOfAssertFactories.BOOLEAN).isFalse();
verify(context.getBean(ContainerCustomizer.class)).configure(listenerContainer);
assertThat(context).hasSingleBean(Environment.class);
});
}
@Configuration(proxyBeanMethods = false)
@EnableRabbit
static class TestConfiguration {
@Test
void whenNativeListenerIsEnabledThenContainerFactoryIsConfiguredToUseNativeListeners() {
this.contextRunner
.withPropertyValues("spring.rabbitmq.listener.type:stream",
"spring.rabbitmq.listener.stream.native-listener:true")
.run((context) -> assertThat(context.getBean(StreamRabbitListenerContainerFactory.class))
.extracting("nativeListener", InstanceOfAssertFactories.BOOLEAN).isTrue());
}
boolean containerCustomizerCalled;
@Test
void whenCustomEnvironmenIsDefinedThenAutoConfiguredEnvironmentBacksOff() {
this.contextRunner.withUserConfiguration(CustomEnvironmentConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(Environment.class);
assertThat(context.getBean(Environment.class))
.isSameAs(context.getBean(CustomEnvironmentConfiguration.class).environment);
});
}
@Test
void whenCustomMessageListenerContainerIsDefinedThenAutoConfiguredContainerBacksOff() {
this.contextRunner.withUserConfiguration(CustomMessageListenerContainerFactoryConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(RabbitListenerContainerFactory.class);
assertThat(context.getBean(RabbitListenerContainerFactory.class)).isSameAs(context.getBean(
CustomMessageListenerContainerFactoryConfiguration.class).listenerContainerFactory);
});
}
@Test
void environmentUsesPropertyDefaultsByDefault() {
EnvironmentBuilder builder = mock(EnvironmentBuilder.class);
RabbitProperties properties = new RabbitProperties();
RabbitStreamConfiguration.configure(builder, properties);
verify(builder).port(5552);
verify(builder).host("localhost");
verify(builder).lazyInitialization(true);
verify(builder).username("guest");
verify(builder).password("guest");
verifyNoMoreInteractions(builder);
}
@Test
void whenStreamPortIsSetThenEnvironmentUsesCustomPort() {
EnvironmentBuilder builder = mock(EnvironmentBuilder.class);
RabbitProperties properties = new RabbitProperties();
properties.getStream().setPort(5553);
RabbitStreamConfiguration.configure(builder, properties);
verify(builder).port(5553);
}
@Test
void whenStreamHostIsSetThenEnvironmentUsesCustomHost() {
EnvironmentBuilder builder = mock(EnvironmentBuilder.class);
RabbitProperties properties = new RabbitProperties();
properties.getStream().setHost("stream.rabbit.example.com");
RabbitStreamConfiguration.configure(builder, properties);
verify(builder).host("stream.rabbit.example.com");
}
@Test
void whenStreamCredentialsAreNotSetThenEnvironmentUsesRabbitCredentials() {
EnvironmentBuilder builder = mock(EnvironmentBuilder.class);
RabbitProperties properties = new RabbitProperties();
properties.setUsername("alice");
properties.setPassword("secret");
RabbitStreamConfiguration.configure(builder, properties);
verify(builder).username("alice");
verify(builder).password("secret");
}
@Test
void whenStreamCredentialsAreSetThenEnvironmentUsesStreamCredentials() {
EnvironmentBuilder builder = mock(EnvironmentBuilder.class);
RabbitProperties properties = new RabbitProperties();
properties.setUsername("alice");
properties.setPassword("secret");
properties.getStream().setUsername("bob");
properties.getStream().setPassword("confidential");
RabbitStreamConfiguration.configure(builder, properties);
verify(builder).username("bob");
verify(builder).password("confidential");
}
@Configuration(proxyBeanMethods = false)
static class TestConfiguration {
@RabbitListener(id = "test", queues = "stream", autoStartup = "false")
void listen(String in) {
@ -70,13 +158,40 @@ class RabbitStreamConfigurationTests {
@Bean
ConsumerCustomizer consumerCustomizer() {
return (id, consumer) -> {
};
return mock(ConsumerCustomizer.class);
}
@Bean
@SuppressWarnings("unchecked")
ContainerCustomizer<StreamListenerContainer> containerCustomizer() {
return (container) -> this.containerCustomizerCalled = true;
return mock(ContainerCustomizer.class);
}
}
@Configuration(proxyBeanMethods = false)
static class CustomEnvironmentConfiguration {
private final Environment environment = Environment.builder().lazyInitialization(true).build();
@Bean
Environment rabbitStreamEnvironment() {
return this.environment;
}
}
@Configuration(proxyBeanMethods = false)
static class CustomMessageListenerContainerFactoryConfiguration {
@SuppressWarnings("rawtypes")
private final RabbitListenerContainerFactory listenerContainerFactory = mock(
RabbitListenerContainerFactory.class);
@Bean
@SuppressWarnings("unchecked")
RabbitListenerContainerFactory<MessageListenerContainer> rabbitListenerContainerFactory() {
return this.listenerContainerFactory;
}
}