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
This commit is contained in:
Phillip Webb 2018-10-02 22:57:09 -07:00
parent 7afde2ba43
commit bd9500290e
2 changed files with 129 additions and 10 deletions

View File

@ -16,6 +16,10 @@
package org.springframework.boot.web.reactive.context;
import java.util.function.Supplier;
import reactor.core.publisher.Mono;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext;
@ -23,6 +27,8 @@ import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.ApplicationContextException;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.StringUtils;
/**
@ -38,6 +44,8 @@ public class ReactiveWebServerApplicationContext
private volatile WebServer webServer;
private volatile DeferredHttpHandler httpHandler;
private String serverNamespace;
/**
@ -78,6 +86,17 @@ public class ReactiveWebServerApplicationContext
}
}
private void createWebServer() {
WebServer localServer = this.webServer;
if (localServer == null) {
DeferredHttpHandler localHandler = new DeferredHttpHandler(
this::getHttpHandler);
this.webServer = getWebServerFactory().getWebServer(localHandler);
this.httpHandler = localHandler;
}
initPropertySources();
}
@Override
protected void finishRefresh() {
super.finishRefresh();
@ -93,14 +112,6 @@ public class ReactiveWebServerApplicationContext
stopAndReleaseReactiveWebServer();
}
private void createWebServer() {
WebServer localServer = this.webServer;
if (localServer == null) {
this.webServer = getWebServerFactory().getWebServer(getHttpHandler());
}
initPropertySources();
}
/**
* Returns the {@link WebServer} that was created by the context or {@code null} if
* the server has not yet been created.
@ -157,7 +168,12 @@ public class ReactiveWebServerApplicationContext
private WebServer startReactiveWebServer() {
WebServer localServer = this.webServer;
DeferredHttpHandler localHandler = this.httpHandler;
if (localServer != null) {
if (localHandler != null) {
localHandler.initialize();
this.httpHandler = null;
}
localServer.start();
}
return localServer;
@ -186,4 +202,40 @@ public class ReactiveWebServerApplicationContext
this.serverNamespace = serverNamespace;
}
/**
* {@link HttpHandler} that defers to a supplied handler which is initialized only
* when the server starts.
*/
static class DeferredHttpHandler implements HttpHandler {
private Supplier<HttpHandler> factory;
private HttpHandler handler;
DeferredHttpHandler(Supplier<HttpHandler> factory) {
this.factory = factory;
this.handler = this::handleUninitialized;
}
public void initialize() {
this.handler = this.factory.get();
}
private Mono<Void> handleUninitialized(ServerHttpRequest request,
ServerHttpResponse response) {
throw new IllegalStateException(
"The HttpHandler has not yet been initialized");
}
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
return this.handler.handle(request, response);
}
public HttpHandler getHandler() {
return this.handler;
}
}
}

View File

@ -18,11 +18,16 @@ package org.springframework.boot.web.reactive.context;
import org.junit.Test;
import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.DeferredHttpHandler;
import org.springframework.boot.web.reactive.context.config.ExampleReactiveWebServerApplicationConfiguration;
import org.springframework.boot.web.reactive.server.MockReactiveWebServerFactory;
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.http.server.reactive.HttpHandler;
import static org.assertj.core.api.Assertions.assertThat;
@ -80,11 +85,23 @@ public class AnnotationConfigReactiveWebServerApplicationContextTests {
verifyContext();
}
@Test
public void httpHandlerInitialization() {
// gh-14666
this.context = new AnnotationConfigReactiveWebServerApplicationContext(
InitializationTestConfig.class);
verifyContext();
}
private void verifyContext() {
MockReactiveWebServerFactory factory = this.context
.getBean(MockReactiveWebServerFactory.class);
HttpHandler httpHandler = this.context.getBean(HttpHandler.class);
assertThat(factory.getWebServer().getHttpHandler()).isEqualTo(httpHandler);
HttpHandler expectedHandler = this.context.getBean(HttpHandler.class);
HttpHandler actualHandler = factory.getWebServer().getHttpHandler();
if (actualHandler instanceof DeferredHttpHandler) {
actualHandler = ((DeferredHttpHandler) actualHandler).getHandler();
}
assertThat(actualHandler).isEqualTo(expectedHandler);
}
@Configuration
@ -107,4 +124,54 @@ public class AnnotationConfigReactiveWebServerApplicationContextTests {
}
@Configuration
public static class InitializationTestConfig {
private static boolean addedListener;
@Bean
public ReactiveWebServerFactory webServerFactory() {
return new MockReactiveWebServerFactory();
}
@Bean
public HttpHandler httpHander() {
if (!addedListener) {
throw new RuntimeException(
"Handlers should be added after listeners, we're being initialized too early!");
}
return mock(HttpHandler.class);
}
@Bean
public Listener listener() {
return new Listener();
}
@Bean
public ApplicationEventMulticaster applicationEventMulticaster() {
return new SimpleApplicationEventMulticaster() {
@Override
public void addApplicationListenerBean(String listenerBeanName) {
super.addApplicationListenerBean(listenerBeanName);
if ("listener".equals(listenerBeanName)) {
addedListener = true;
}
}
};
}
private static class Listener
implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
}
}
}
}