Skip to content

Commit 7a0fe0f

Browse files
committed
Polish "Add auto-configuration for spring-rabbit-stream"
See gh-27480
1 parent 9784838 commit 7a0fe0f

File tree

3 files changed

+159
-44
lines changed

3 files changed

+159
-44
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitProperties.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -897,8 +897,8 @@ public void setMissingQueuesFatal(boolean missingQueuesFatal) {
897897
public static class StreamContainer extends BaseContainer {
898898

899899
/**
900-
* When true, the container factory will create containers that support listeners
901-
* that consume native stream messages instead of spring-amqp {@code Message}s.
900+
* Whether the container will support listeners that consume native stream
901+
* messages instead of Spring AMQP messages.
902902
*/
903903
boolean nativeListener;
904904

@@ -1172,24 +1172,24 @@ private boolean determineSslEnabled(boolean sslEnabled) {
11721172
public static final class Stream {
11731173

11741174
/**
1175-
* Host of a RabbitMQ instance with the Stream Plugin Enabled
1175+
* Host of a RabbitMQ instance with the Stream plugin enabled.
11761176
*/
11771177
private String host = "localhost";
11781178

11791179
/**
1180-
* Stream port of a RabbitMQ instance with the Stream Plugin Enabled
1180+
* Stream port of a RabbitMQ instance with the Stream plugin enabled.
11811181
*/
11821182
private int port = DEFAULT_STREAM_PORT;
11831183

11841184
/**
1185-
* Login user to authenticate to the broker. If not set
1186-
* {@code spring.rabbitmq.username} will be used.
1185+
* Login user to authenticate to the broker. When not set,
1186+
* spring.rabbitmq.username is used.
11871187
*/
11881188
private String username;
11891189

11901190
/**
1191-
* Login password to authenticate to the broker. If not set
1192-
* {@code spring.rabbitmq.password} will be used.
1191+
* Login password to authenticate to the broker. When not set
1192+
* spring.rabbitmq.password is used.
11931193
*/
11941194
private String password;
11951195

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitStreamConfiguration.java

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,26 @@
1616

1717
package org.springframework.boot.autoconfigure.amqp;
1818

19+
import java.util.function.Function;
20+
import java.util.function.Supplier;
21+
1922
import com.rabbitmq.stream.Environment;
2023
import com.rabbitmq.stream.EnvironmentBuilder;
2124

22-
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
2325
import org.springframework.amqp.rabbit.config.ContainerCustomizer;
2426
import org.springframework.beans.factory.ObjectProvider;
2527
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2628
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2729
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
30+
import org.springframework.boot.context.properties.PropertyMapper;
2831
import org.springframework.context.annotation.Bean;
2932
import org.springframework.context.annotation.Configuration;
3033
import org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory;
3134
import org.springframework.rabbit.stream.listener.ConsumerCustomizer;
3235
import org.springframework.rabbit.stream.listener.StreamListenerContainer;
3336

3437
/**
35-
* Configuration for Spring RabbitMQ Stream Plugin support.
38+
* Configuration for Spring RabbitMQ Stream plugin support.
3639
*
3740
* @author Gary Russell
3841
*/
@@ -46,7 +49,6 @@ class RabbitStreamConfiguration {
4649
StreamRabbitListenerContainerFactory streamRabbitListenerContainerFactory(Environment rabbitStreamEnvironment,
4750
RabbitProperties properties, ObjectProvider<ConsumerCustomizer> consumerCustomizer,
4851
ObjectProvider<ContainerCustomizer<StreamListenerContainer>> containerCustomizer) {
49-
5052
StreamRabbitListenerContainerFactory factory = new StreamRabbitListenerContainerFactory(
5153
rabbitStreamEnvironment);
5254
factory.setNativeListener(properties.getListener().getStream().isNativeListener());
@@ -58,24 +60,22 @@ StreamRabbitListenerContainerFactory streamRabbitListenerContainerFactory(Enviro
5860
@Bean(name = "rabbitStreamEnvironment")
5961
@ConditionalOnMissingBean(name = "rabbitStreamEnvironment")
6062
Environment rabbitStreamEnvironment(RabbitProperties properties) {
63+
return configure(Environment.builder(), properties).build();
64+
}
65+
66+
static EnvironmentBuilder configure(EnvironmentBuilder builder, RabbitProperties properties) {
67+
builder.lazyInitialization(true);
6168
RabbitProperties.Stream stream = properties.getStream();
62-
String username = stream.getUsername();
63-
if (username == null) {
64-
username = properties.getUsername();
65-
}
66-
String password = stream.getPassword();
67-
if (password == null) {
68-
password = properties.getPassword();
69-
}
70-
EnvironmentBuilder builder = Environment.builder().lazyInitialization(true).host(stream.getHost())
71-
.port(stream.getPort());
72-
if (username != null) {
73-
builder.username(username);
74-
}
75-
if (password != null) {
76-
builder.password(password);
77-
}
78-
return builder.build();
69+
PropertyMapper mapper = PropertyMapper.get();
70+
mapper.from(stream.getHost()).to(builder::host);
71+
mapper.from(stream.getPort()).to(builder::port);
72+
mapper.from(stream.getUsername()).as(withFallback(properties::getUsername)).whenNonNull().to(builder::username);
73+
mapper.from(stream.getPassword()).as(withFallback(properties::getPassword)).whenNonNull().to(builder::password);
74+
return builder;
75+
}
76+
77+
private static Function<String, String> withFallback(Supplier<String> fallback) {
78+
return (value) -> (value != null) ? value : fallback.get();
7979
}
8080

8181
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitStreamConfigurationTests.java

Lines changed: 131 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616

1717
package org.springframework.boot.autoconfigure.amqp;
1818

19+
import com.rabbitmq.stream.Environment;
20+
import com.rabbitmq.stream.EnvironmentBuilder;
21+
import org.assertj.core.api.InstanceOfAssertFactories;
1922
import org.junit.jupiter.api.Test;
20-
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
23+
2124
import org.springframework.amqp.rabbit.annotation.RabbitListener;
2225
import org.springframework.amqp.rabbit.config.ContainerCustomizer;
26+
import org.springframework.amqp.rabbit.listener.MessageListenerContainer;
27+
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
2328
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
24-
import org.springframework.beans.DirectFieldAccessor;
2529
import org.springframework.boot.autoconfigure.AutoConfigurations;
2630
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
2731
import org.springframework.context.annotation.Bean;
@@ -31,52 +35,163 @@
3135
import org.springframework.rabbit.stream.listener.StreamListenerContainer;
3236

3337
import static org.assertj.core.api.Assertions.assertThat;
38+
import static org.mockito.Mockito.mock;
39+
import static org.mockito.Mockito.verify;
40+
import static org.mockito.Mockito.verifyNoMoreInteractions;
3441

3542
/**
3643
* Tests for {@link RabbitStreamConfiguration}.
3744
*
3845
* @author Gary Russell
46+
* @author Andy Wilkinson
3947
*/
4048
class RabbitStreamConfigurationTests {
4149

4250
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
4351
.withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class));
4452

4553
@Test
46-
void testContainerType() {
54+
@SuppressWarnings("unchecked")
55+
void whenListenerTypeIsStreamThenStreamListenerContainerAndEnvironmentAreAutoConfigured() {
4756
this.contextRunner.withUserConfiguration(TestConfiguration.class)
57+
.withPropertyValues("spring.rabbitmq.listener.type:stream").run((context) -> {
58+
RabbitListenerEndpointRegistry registry = context.getBean(RabbitListenerEndpointRegistry.class);
59+
MessageListenerContainer listenerContainer = registry.getListenerContainer("test");
60+
assertThat(listenerContainer).isInstanceOf(StreamListenerContainer.class);
61+
assertThat(listenerContainer).extracting("consumerCustomizer").isNotNull();
62+
assertThat(context.getBean(StreamRabbitListenerContainerFactory.class))
63+
.extracting("nativeListener", InstanceOfAssertFactories.BOOLEAN).isFalse();
64+
verify(context.getBean(ContainerCustomizer.class)).configure(listenerContainer);
65+
assertThat(context).hasSingleBean(Environment.class);
66+
});
67+
}
68+
69+
@Test
70+
void whenNativeListenerIsEnabledThenContainerFactoryIsConfiguredToUseNativeListeners() {
71+
this.contextRunner
4872
.withPropertyValues("spring.rabbitmq.listener.type:stream",
4973
"spring.rabbitmq.listener.stream.native-listener:true")
74+
.run((context) -> assertThat(context.getBean(StreamRabbitListenerContainerFactory.class))
75+
.extracting("nativeListener", InstanceOfAssertFactories.BOOLEAN).isTrue());
76+
}
77+
78+
@Test
79+
void whenCustomEnvironmenIsDefinedThenAutoConfiguredEnvironmentBacksOff() {
80+
this.contextRunner.withUserConfiguration(CustomEnvironmentConfiguration.class).run((context) -> {
81+
assertThat(context).hasSingleBean(Environment.class);
82+
assertThat(context.getBean(Environment.class))
83+
.isSameAs(context.getBean(CustomEnvironmentConfiguration.class).environment);
84+
});
85+
}
86+
87+
@Test
88+
void whenCustomMessageListenerContainerIsDefinedThenAutoConfiguredContainerBacksOff() {
89+
this.contextRunner.withUserConfiguration(CustomMessageListenerContainerFactoryConfiguration.class)
5090
.run((context) -> {
51-
RabbitListenerEndpointRegistry registry = context.getBean(RabbitListenerEndpointRegistry.class);
52-
assertThat(registry.getListenerContainer("test")).isInstanceOf(StreamListenerContainer.class);
53-
assertThat(new DirectFieldAccessor(registry.getListenerContainer("test"))
54-
.getPropertyValue("consumerCustomizer")).isNotNull();
55-
assertThat(new DirectFieldAccessor(context.getBean(StreamRabbitListenerContainerFactory.class))
56-
.getPropertyValue("nativeListener")).isEqualTo(Boolean.TRUE);
57-
assertThat(context.getBean(TestConfiguration.class).containerCustomizerCalled).isTrue();
91+
assertThat(context).hasSingleBean(RabbitListenerContainerFactory.class);
92+
assertThat(context.getBean(RabbitListenerContainerFactory.class)).isSameAs(context.getBean(
93+
CustomMessageListenerContainerFactoryConfiguration.class).listenerContainerFactory);
5894
});
5995
}
6096

97+
@Test
98+
void environmentUsesPropertyDefaultsByDefault() {
99+
EnvironmentBuilder builder = mock(EnvironmentBuilder.class);
100+
RabbitProperties properties = new RabbitProperties();
101+
RabbitStreamConfiguration.configure(builder, properties);
102+
verify(builder).port(5552);
103+
verify(builder).host("localhost");
104+
verify(builder).lazyInitialization(true);
105+
verify(builder).username("guest");
106+
verify(builder).password("guest");
107+
verifyNoMoreInteractions(builder);
108+
}
109+
110+
@Test
111+
void whenStreamPortIsSetThenEnvironmentUsesCustomPort() {
112+
EnvironmentBuilder builder = mock(EnvironmentBuilder.class);
113+
RabbitProperties properties = new RabbitProperties();
114+
properties.getStream().setPort(5553);
115+
RabbitStreamConfiguration.configure(builder, properties);
116+
verify(builder).port(5553);
117+
}
118+
119+
@Test
120+
void whenStreamHostIsSetThenEnvironmentUsesCustomHost() {
121+
EnvironmentBuilder builder = mock(EnvironmentBuilder.class);
122+
RabbitProperties properties = new RabbitProperties();
123+
properties.getStream().setHost("stream.rabbit.example.com");
124+
RabbitStreamConfiguration.configure(builder, properties);
125+
verify(builder).host("stream.rabbit.example.com");
126+
}
127+
128+
@Test
129+
void whenStreamCredentialsAreNotSetThenEnvironmentUsesRabbitCredentials() {
130+
EnvironmentBuilder builder = mock(EnvironmentBuilder.class);
131+
RabbitProperties properties = new RabbitProperties();
132+
properties.setUsername("alice");
133+
properties.setPassword("secret");
134+
RabbitStreamConfiguration.configure(builder, properties);
135+
verify(builder).username("alice");
136+
verify(builder).password("secret");
137+
}
138+
139+
@Test
140+
void whenStreamCredentialsAreSetThenEnvironmentUsesStreamCredentials() {
141+
EnvironmentBuilder builder = mock(EnvironmentBuilder.class);
142+
RabbitProperties properties = new RabbitProperties();
143+
properties.setUsername("alice");
144+
properties.setPassword("secret");
145+
properties.getStream().setUsername("bob");
146+
properties.getStream().setPassword("confidential");
147+
RabbitStreamConfiguration.configure(builder, properties);
148+
verify(builder).username("bob");
149+
verify(builder).password("confidential");
150+
}
151+
61152
@Configuration(proxyBeanMethods = false)
62-
@EnableRabbit
63153
static class TestConfiguration {
64154

65-
boolean containerCustomizerCalled;
66-
67155
@RabbitListener(id = "test", queues = "stream", autoStartup = "false")
68156
void listen(String in) {
69157
}
70158

71159
@Bean
72160
ConsumerCustomizer consumerCustomizer() {
73-
return (id, consumer) -> {
74-
};
161+
return mock(ConsumerCustomizer.class);
75162
}
76163

77164
@Bean
165+
@SuppressWarnings("unchecked")
78166
ContainerCustomizer<StreamListenerContainer> containerCustomizer() {
79-
return (container) -> this.containerCustomizerCalled = true;
167+
return mock(ContainerCustomizer.class);
168+
}
169+
170+
}
171+
172+
@Configuration(proxyBeanMethods = false)
173+
static class CustomEnvironmentConfiguration {
174+
175+
private final Environment environment = Environment.builder().lazyInitialization(true).build();
176+
177+
@Bean
178+
Environment rabbitStreamEnvironment() {
179+
return this.environment;
180+
}
181+
182+
}
183+
184+
@Configuration(proxyBeanMethods = false)
185+
static class CustomMessageListenerContainerFactoryConfiguration {
186+
187+
@SuppressWarnings("rawtypes")
188+
private final RabbitListenerContainerFactory listenerContainerFactory = mock(
189+
RabbitListenerContainerFactory.class);
190+
191+
@Bean
192+
@SuppressWarnings("unchecked")
193+
RabbitListenerContainerFactory<MessageListenerContainer> rabbitListenerContainerFactory() {
194+
return this.listenerContainerFactory;
80195
}
81196

82197
}

0 commit comments

Comments
 (0)