From 250a1601d744a0772b68e6aa180bad7ab10d1a08 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Thu, 26 Jan 2017 15:31:59 +0100 Subject: [PATCH] Add WebApplicationType.REACTIVE type This commit introduces a new variant of web applications: there are now Servlet based and Reactive web applications. The mere presence of `Servlet` and `ConfigurableWebApplicationContext` classes is not enough to make a difference between those variants. This is why the decision process is now the following: * if `DispatcherHandler` is present but not `DispatcherServlet`, the WebApplicationType is detected as REACTIVE * if `DispatcherHandler` is present and `DispatcherServlet`, this is a case where we consider that developers are using Spring MVC in combination with the reactive web client. So WebApplicationType is detected as SERVLET * if `Servlet` and `ConfigurableWebApplicationContext` are present, WebApplicationType is detected as SERVLET * if none of the above match, WebApplicationType is NONE Fixes gh-8017 --- .../SpringBootTestContextBootstrapper.java | 24 ++++++++++---- spring-boot/pom.xml | 5 +++ .../boot/SpringApplication.java | 31 +++++++++++++++++-- .../boot/WebApplicationType.java | 10 +++++- .../ReactiveWebApplicationContext.java | 28 +++++++++++++++++ .../boot/SpringApplicationTests.java | 11 +++++++ 6 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebApplicationContext.java diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java b/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java index 31c5c2fd57..0cb252a850 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java @@ -26,6 +26,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.WebApplicationType; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.env.Environment; @@ -58,6 +59,7 @@ import org.springframework.util.ObjectUtils; * * @author Phillip Webb * @author Andy Wilkinson + * @author Brian Clozel * @since 1.4.0 * @see SpringBootTest * @see TestConfiguration @@ -67,6 +69,12 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" }; + private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework." + + "web.reactive.DispatcherHandler"; + + private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework." + + "web.servlet.DispatcherServlet"; + private static final String ACTIVATE_SERVLET_LISTENER = "org.springframework.test." + "context.web.ServletTestExecutionListener.activateListener"; @@ -78,7 +86,7 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr TestContext context = super.buildTestContext(); verifyConfiguration(context.getTestClass()); WebEnvironment webEnvironment = getWebEnvironment(context.getTestClass()); - if (webEnvironment == WebEnvironment.MOCK && hasWebEnvironmentClasses()) { + if (webEnvironment == WebEnvironment.MOCK && deduceWebApplication() == WebApplicationType.SERVLET) { context.setAttribute(ACTIVATE_SERVLET_LISTENER, true); } else if (webEnvironment != null && webEnvironment.isEmbedded()) { @@ -138,8 +146,8 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr .toArray(new String[propertySourceProperties.size()])); WebEnvironment webEnvironment = getWebEnvironment(mergedConfig.getTestClass()); if (webEnvironment != null) { - if (webEnvironment.isEmbedded() || (webEnvironment == WebEnvironment.MOCK - && hasWebEnvironmentClasses())) { + if (deduceWebApplication() == WebApplicationType.SERVLET && + (webEnvironment.isEmbedded() || webEnvironment == WebEnvironment.MOCK)) { WebAppConfiguration webAppConfiguration = AnnotatedElementUtils .findMergedAnnotation(mergedConfig.getTestClass(), WebAppConfiguration.class); @@ -152,13 +160,17 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr return mergedConfig; } - private boolean hasWebEnvironmentClasses() { + private WebApplicationType deduceWebApplication() { + if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null) + && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) { + return WebApplicationType.REACTIVE; + } for (String className : WEB_ENVIRONMENT_CLASSES) { if (!ClassUtils.isPresent(className, null)) { - return false; + return WebApplicationType.NONE; } } - return true; + return WebApplicationType.SERVLET; } protected Class[] getOrFindConfigurationClasses( diff --git a/spring-boot/pom.xml b/spring-boot/pom.xml index c8ff29d263..34ef7182fa 100644 --- a/spring-boot/pom.xml +++ b/spring-boot/pom.xml @@ -209,6 +209,11 @@ spring-webmvc true + + org.springframework + spring-webflux + true + org.yaml snakeyaml diff --git a/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java b/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java index b8d2585fd9..5777694b28 100644 --- a/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -153,6 +153,7 @@ import org.springframework.web.context.support.StandardServletEnvironment; * @author Craig Burke * @author Michael Simons * @author Madhura Bhave + * @author Brian Clozel * @see #run(Object, String[]) * @see #run(Object[], String[]) * @see #SpringApplication(Object...) @@ -176,6 +177,19 @@ public class SpringApplication { private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" }; + /** + * The class name of application context that will be used by default for + * reactive web environments. + */ + public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework." + + "boot.context.embedded.ReactiveWebApplicationContext"; + + private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework." + + "web.reactive.DispatcherHandler"; + + private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework." + + "web.servlet.DispatcherServlet"; + /** * Default banner location. */ @@ -277,6 +291,10 @@ public class SpringApplication { } private WebApplicationType deduceWebApplication() { + if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null) + && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) { + return WebApplicationType.REACTIVE; + } for (String className : WEB_ENVIRONMENT_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; @@ -606,9 +624,16 @@ public class SpringApplication { Class contextClass = this.applicationContextClass; if (contextClass == null) { try { - contextClass = Class - .forName(this.webApplicationType == WebApplicationType.SERVLET - ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS); + switch (this.webApplicationType) { + case SERVLET: + contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS); + break; + case REACTIVE: + contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); + break; + default: + contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); + } } catch (ClassNotFoundException ex) { throw new IllegalStateException( diff --git a/spring-boot/src/main/java/org/springframework/boot/WebApplicationType.java b/spring-boot/src/main/java/org/springframework/boot/WebApplicationType.java index 1f56886cd3..ced8ebb4e3 100644 --- a/spring-boot/src/main/java/org/springframework/boot/WebApplicationType.java +++ b/spring-boot/src/main/java/org/springframework/boot/WebApplicationType.java @@ -20,6 +20,7 @@ package org.springframework.boot; * An enumeration of possible types of web application. * * @author Andy Wilkinson + * @author Brian Clozel * @since 2.0.0 */ public enum WebApplicationType { @@ -34,6 +35,13 @@ public enum WebApplicationType { * The application should run as a servlet-based web application and should start an * embedded servlet container. */ - SERVLET; + SERVLET, + + /** + * The application should run as a reactive web application and should start + * an embedded web container. + */ + REACTIVE; + } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebApplicationContext.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebApplicationContext.java new file mode 100644 index 0000000000..35e39893d4 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebApplicationContext.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.context.embedded; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +/** + * A {@link AnnotationConfigApplicationContext} that can be used to bootstrap itself from a contained + * embedded web server factory bean. + * @author Brian Clozel + * @since 2.0.0 + */ +public class ReactiveWebApplicationContext extends AnnotationConfigApplicationContext { +} diff --git a/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java index a8f4ae5832..acc9a002be 100644 --- a/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java @@ -43,6 +43,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.DefaultBeanNameGenerator; import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; +import org.springframework.boot.context.embedded.ReactiveWebApplicationContext; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationPreparedEvent; @@ -102,6 +103,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; * @author Jeremy Rickard * @author Craig Burke * @author Madhura Bhave + * @author Brian Clozel */ public class SpringApplicationTests { @@ -395,6 +397,15 @@ public class SpringApplicationTests { .isInstanceOf(AnnotationConfigEmbeddedWebApplicationContext.class); } + @Test + public void defaultApplicationContextForReactiveWeb() throws Exception { + SpringApplication application = new SpringApplication(ExampleWebConfig.class); + application.setWebApplicationType(WebApplicationType.REACTIVE); + this.context = application.run(); + assertThat(this.context) + .isInstanceOf(ReactiveWebApplicationContext.class); + } + @Test public void customEnvironment() throws Exception { TestSpringApplication application = new TestSpringApplication(