diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java index 6b026fcdc09..a2e4227916b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java @@ -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 factory; + + private HttpHandler handler; + + DeferredHttpHandler(Supplier factory) { + this.factory = factory; + this.handler = this::handleUninitialized; + } + + public void initialize() { + this.handler = this.factory.get(); + } + + private Mono handleUninitialized(ServerHttpRequest request, + ServerHttpResponse response) { + throw new IllegalStateException( + "The HttpHandler has not yet been initialized"); + } + + @Override + public Mono handle(ServerHttpRequest request, ServerHttpResponse response) { + return this.handler.handle(request, response); + } + + public HttpHandler getHandler() { + return this.handler; + } + + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebServerApplicationContextTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebServerApplicationContextTests.java index 3eef76f068c..6556fe697ca 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebServerApplicationContextTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebServerApplicationContextTests.java @@ -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 { + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + } + + } + + } + }