Skip to content

Commit dcf61ab

Browse files
committed
Fix Reactive Server auto-configuration ordering
This commit ensures that Tomcat is the first reactive server configured if the Tomcat dependency is on classpath. Spring Boot chose Reactor Netty as the default for the reactive web starter, but the Reactor Netty dependency can be used also for its HTTP client. In case developers are adding Tomcat, Undertow or Jetty on their classpath, we must configure those and consider Reactor Netty for the client only. Fixes spring-projectsgh-12176
1 parent 3fddfee commit dcf61ab

File tree

2 files changed

+54
-43
lines changed

2 files changed

+54
-43
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfiguration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@
5050
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
5151
@EnableConfigurationProperties(ServerProperties.class)
5252
@Import({ ReactiveWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
53-
ReactiveWebServerFactoryConfiguration.EmbeddedNetty.class,
5453
ReactiveWebServerFactoryConfiguration.EmbeddedTomcat.class,
5554
ReactiveWebServerFactoryConfiguration.EmbeddedJetty.class,
56-
ReactiveWebServerFactoryConfiguration.EmbeddedUndertow.class })
55+
ReactiveWebServerFactoryConfiguration.EmbeddedUndertow.class,
56+
ReactiveWebServerFactoryConfiguration.EmbeddedNetty.class })
5757
public class ReactiveWebServerFactoryAutoConfiguration {
5858

5959
@Bean

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfigurationTests.java

Lines changed: 52 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,19 @@
1616

1717
package org.springframework.boot.autoconfigure.web.reactive;
1818

19-
import org.hamcrest.Matchers;
20-
import org.junit.Rule;
2119
import org.junit.Test;
22-
import org.junit.rules.ExpectedException;
2320
import org.mockito.Mockito;
2421

22+
import org.springframework.boot.autoconfigure.AutoConfigurations;
23+
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
24+
import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory;
2525
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
2626
import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory;
2727
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
2828
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
2929
import org.springframework.context.ApplicationContextException;
3030
import org.springframework.context.annotation.Bean;
3131
import org.springframework.context.annotation.Configuration;
32-
import org.springframework.context.annotation.Import;
3332
import org.springframework.http.server.reactive.HttpHandler;
3433

3534
import static org.assertj.core.api.Assertions.assertThat;
@@ -41,54 +40,73 @@
4140
*/
4241
public class ReactiveWebServerFactoryAutoConfigurationTests {
4342

44-
private AnnotationConfigReactiveWebServerApplicationContext context;
45-
46-
@Rule
47-
public ExpectedException thrown = ExpectedException.none();
43+
private ReactiveWebApplicationContextRunner contextRunner =
44+
new ReactiveWebApplicationContextRunner(AnnotationConfigReactiveWebServerApplicationContext::new)
45+
.withConfiguration(AutoConfigurations.of(ReactiveWebServerFactoryAutoConfiguration.class));
4846

4947
@Test
5048
public void createFromConfigClass() {
51-
this.context = new AnnotationConfigReactiveWebServerApplicationContext(
52-
BaseConfiguration.class);
53-
assertThat(this.context.getBeansOfType(ReactiveWebServerFactory.class))
54-
.hasSize(1);
55-
assertThat(this.context.getBeansOfType(WebServerFactoryCustomizer.class))
56-
.hasSize(1);
57-
assertThat(this.context
58-
.getBeansOfType(ReactiveWebServerFactoryCustomizer.class))
59-
.hasSize(1);
49+
this.contextRunner
50+
.withUserConfiguration(MockWebServerAutoConfiguration.class,
51+
HttpHandlerConfiguration.class)
52+
.run(context -> {
53+
assertThat(context.getBeansOfType(ReactiveWebServerFactory.class))
54+
.hasSize(1);
55+
assertThat(context.getBeansOfType(WebServerFactoryCustomizer.class))
56+
.hasSize(1);
57+
assertThat(context.getBeansOfType(ReactiveWebServerFactoryCustomizer.class))
58+
.hasSize(1);
59+
});
6060
}
6161

6262
@Test
6363
public void missingHttpHandler() {
64-
this.thrown.expect(ApplicationContextException.class);
65-
this.thrown.expectMessage(Matchers.containsString("missing HttpHandler bean"));
66-
this.context = new AnnotationConfigReactiveWebServerApplicationContext(
67-
MissingHttpHandlerConfiguration.class);
64+
this.contextRunner
65+
.withUserConfiguration(MockWebServerAutoConfiguration.class)
66+
.run(context -> {
67+
assertThat(context.getStartupFailure())
68+
.isInstanceOf(ApplicationContextException.class)
69+
.hasMessageContaining("missing HttpHandler bean");
70+
});
6871
}
6972

7073
@Test
7174
public void multipleHttpHandler() {
72-
this.thrown.expect(ApplicationContextException.class);
73-
this.thrown.expectMessage(Matchers.containsString(
74-
"multiple HttpHandler beans : httpHandler,additionalHttpHandler"));
75-
this.context = new AnnotationConfigReactiveWebServerApplicationContext(
76-
BaseConfiguration.class, TooManyHttpHandlers.class);
75+
this.contextRunner
76+
.withUserConfiguration(MockWebServerAutoConfiguration.class,
77+
HttpHandlerConfiguration.class, TooManyHttpHandlers.class)
78+
.run(context -> {
79+
assertThat(context.getStartupFailure())
80+
.isInstanceOf(ApplicationContextException.class)
81+
.hasMessageContaining("multiple HttpHandler beans : " +
82+
"httpHandler,additionalHttpHandler");
83+
});
7784
}
7885

7986
@Test
8087
public void customizeReactiveWebServer() {
81-
this.context = new AnnotationConfigReactiveWebServerApplicationContext(
82-
BaseConfiguration.class, ReactiveWebServerCustomization.class);
83-
MockReactiveWebServerFactory factory = this.context
84-
.getBean(MockReactiveWebServerFactory.class);
85-
assertThat(factory.getPort()).isEqualTo(9000);
88+
this.contextRunner
89+
.withUserConfiguration(MockWebServerAutoConfiguration.class,
90+
HttpHandlerConfiguration.class, ReactiveWebServerCustomization.class)
91+
.run(context -> {
92+
assertThat(context.getBean(MockReactiveWebServerFactory.class).getPort())
93+
.isEqualTo(9000);
94+
});
95+
}
96+
97+
@Test
98+
public void defaultWebServerIsTomcat() {
99+
// Tomcat should be chosen over Netty if the Tomcat library is present.
100+
this.contextRunner
101+
.withUserConfiguration(HttpHandlerConfiguration.class)
102+
.run(context -> {
103+
assertThat(context.getBean(ReactiveWebServerFactory.class))
104+
.isInstanceOf(TomcatReactiveWebServerFactory.class);
105+
});
86106
}
87107

88108
@Configuration
89-
@Import({ MockWebServerAutoConfiguration.class,
90-
ReactiveWebServerFactoryAutoConfiguration.class })
91-
protected static class BaseConfiguration {
109+
protected static class HttpHandlerConfiguration {
92110

93111
@Bean
94112
public HttpHandler httpHandler() {
@@ -97,13 +115,6 @@ public HttpHandler httpHandler() {
97115

98116
}
99117

100-
@Configuration
101-
@Import({ MockWebServerAutoConfiguration.class,
102-
ReactiveWebServerFactoryAutoConfiguration.class })
103-
protected static class MissingHttpHandlerConfiguration {
104-
105-
}
106-
107118
@Configuration
108119
protected static class TooManyHttpHandlers {
109120

0 commit comments

Comments
 (0)