Skip to content

Commit bd95002

Browse files
committed
Defer HttpHandler initialization
Update `ReactiveWebServerApplicationContext` so that the `HttpHandler` bean is not longer created from `onRefresh`, but is instead created only when the server starts. Prior to this commit, the WebFlux hander would cause early initialization of several beans, including Jackson Modules. Closes gh-14666
1 parent 7afde2b commit bd95002

File tree

2 files changed

+129
-10
lines changed

2 files changed

+129
-10
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,19 @@
1616

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

19+
import java.util.function.Supplier;
20+
21+
import reactor.core.publisher.Mono;
22+
1923
import org.springframework.beans.BeansException;
2024
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
2125
import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext;
2226
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
2327
import org.springframework.boot.web.server.WebServer;
2428
import org.springframework.context.ApplicationContextException;
2529
import org.springframework.http.server.reactive.HttpHandler;
30+
import org.springframework.http.server.reactive.ServerHttpRequest;
31+
import org.springframework.http.server.reactive.ServerHttpResponse;
2632
import org.springframework.util.StringUtils;
2733

2834
/**
@@ -38,6 +44,8 @@ public class ReactiveWebServerApplicationContext
3844

3945
private volatile WebServer webServer;
4046

47+
private volatile DeferredHttpHandler httpHandler;
48+
4149
private String serverNamespace;
4250

4351
/**
@@ -78,6 +86,17 @@ protected void onRefresh() {
7886
}
7987
}
8088

89+
private void createWebServer() {
90+
WebServer localServer = this.webServer;
91+
if (localServer == null) {
92+
DeferredHttpHandler localHandler = new DeferredHttpHandler(
93+
this::getHttpHandler);
94+
this.webServer = getWebServerFactory().getWebServer(localHandler);
95+
this.httpHandler = localHandler;
96+
}
97+
initPropertySources();
98+
}
99+
81100
@Override
82101
protected void finishRefresh() {
83102
super.finishRefresh();
@@ -93,14 +112,6 @@ protected void onClose() {
93112
stopAndReleaseReactiveWebServer();
94113
}
95114

96-
private void createWebServer() {
97-
WebServer localServer = this.webServer;
98-
if (localServer == null) {
99-
this.webServer = getWebServerFactory().getWebServer(getHttpHandler());
100-
}
101-
initPropertySources();
102-
}
103-
104115
/**
105116
* Returns the {@link WebServer} that was created by the context or {@code null} if
106117
* the server has not yet been created.
@@ -157,7 +168,12 @@ protected HttpHandler getHttpHandler() {
157168

158169
private WebServer startReactiveWebServer() {
159170
WebServer localServer = this.webServer;
171+
DeferredHttpHandler localHandler = this.httpHandler;
160172
if (localServer != null) {
173+
if (localHandler != null) {
174+
localHandler.initialize();
175+
this.httpHandler = null;
176+
}
161177
localServer.start();
162178
}
163179
return localServer;
@@ -186,4 +202,40 @@ public void setServerNamespace(String serverNamespace) {
186202
this.serverNamespace = serverNamespace;
187203
}
188204

205+
/**
206+
* {@link HttpHandler} that defers to a supplied handler which is initialized only
207+
* when the server starts.
208+
*/
209+
static class DeferredHttpHandler implements HttpHandler {
210+
211+
private Supplier<HttpHandler> factory;
212+
213+
private HttpHandler handler;
214+
215+
DeferredHttpHandler(Supplier<HttpHandler> factory) {
216+
this.factory = factory;
217+
this.handler = this::handleUninitialized;
218+
}
219+
220+
public void initialize() {
221+
this.handler = this.factory.get();
222+
}
223+
224+
private Mono<Void> handleUninitialized(ServerHttpRequest request,
225+
ServerHttpResponse response) {
226+
throw new IllegalStateException(
227+
"The HttpHandler has not yet been initialized");
228+
}
229+
230+
@Override
231+
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
232+
return this.handler.handle(request, response);
233+
}
234+
235+
public HttpHandler getHandler() {
236+
return this.handler;
237+
}
238+
239+
}
240+
189241
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebServerApplicationContextTests.java

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,16 @@
1818

1919
import org.junit.Test;
2020

21+
import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.DeferredHttpHandler;
2122
import org.springframework.boot.web.reactive.context.config.ExampleReactiveWebServerApplicationConfiguration;
2223
import org.springframework.boot.web.reactive.server.MockReactiveWebServerFactory;
2324
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
25+
import org.springframework.context.ApplicationListener;
2426
import org.springframework.context.annotation.Bean;
2527
import org.springframework.context.annotation.Configuration;
28+
import org.springframework.context.event.ApplicationEventMulticaster;
29+
import org.springframework.context.event.ContextRefreshedEvent;
30+
import org.springframework.context.event.SimpleApplicationEventMulticaster;
2631
import org.springframework.http.server.reactive.HttpHandler;
2732

2833
import static org.assertj.core.api.Assertions.assertThat;
@@ -80,11 +85,23 @@ public void scanAndRefresh() {
8085
verifyContext();
8186
}
8287

88+
@Test
89+
public void httpHandlerInitialization() {
90+
// gh-14666
91+
this.context = new AnnotationConfigReactiveWebServerApplicationContext(
92+
InitializationTestConfig.class);
93+
verifyContext();
94+
}
95+
8396
private void verifyContext() {
8497
MockReactiveWebServerFactory factory = this.context
8598
.getBean(MockReactiveWebServerFactory.class);
86-
HttpHandler httpHandler = this.context.getBean(HttpHandler.class);
87-
assertThat(factory.getWebServer().getHttpHandler()).isEqualTo(httpHandler);
99+
HttpHandler expectedHandler = this.context.getBean(HttpHandler.class);
100+
HttpHandler actualHandler = factory.getWebServer().getHttpHandler();
101+
if (actualHandler instanceof DeferredHttpHandler) {
102+
actualHandler = ((DeferredHttpHandler) actualHandler).getHandler();
103+
}
104+
assertThat(actualHandler).isEqualTo(expectedHandler);
88105
}
89106

90107
@Configuration
@@ -107,4 +124,54 @@ public HttpHandler httpHandler() {
107124

108125
}
109126

127+
@Configuration
128+
public static class InitializationTestConfig {
129+
130+
private static boolean addedListener;
131+
132+
@Bean
133+
public ReactiveWebServerFactory webServerFactory() {
134+
return new MockReactiveWebServerFactory();
135+
}
136+
137+
@Bean
138+
public HttpHandler httpHander() {
139+
if (!addedListener) {
140+
throw new RuntimeException(
141+
"Handlers should be added after listeners, we're being initialized too early!");
142+
}
143+
return mock(HttpHandler.class);
144+
}
145+
146+
@Bean
147+
public Listener listener() {
148+
return new Listener();
149+
}
150+
151+
@Bean
152+
public ApplicationEventMulticaster applicationEventMulticaster() {
153+
return new SimpleApplicationEventMulticaster() {
154+
155+
@Override
156+
public void addApplicationListenerBean(String listenerBeanName) {
157+
super.addApplicationListenerBean(listenerBeanName);
158+
if ("listener".equals(listenerBeanName)) {
159+
addedListener = true;
160+
}
161+
}
162+
163+
};
164+
}
165+
166+
private static class Listener
167+
implements ApplicationListener<ContextRefreshedEvent> {
168+
169+
@Override
170+
public void onApplicationEvent(ContextRefreshedEvent event) {
171+
}
172+
173+
}
174+
175+
}
176+
110177
}

0 commit comments

Comments
 (0)