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
This commit is contained in:
		
							parent
							
								
									0aaea05a4b
								
							
						
					
					
						commit
						250a1601d7
					
				|  | @ -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( | ||||
|  |  | |||
|  | @ -209,6 +209,11 @@ | |||
| 			<artifactId>spring-webmvc</artifactId> | ||||
| 			<optional>true</optional> | ||||
| 		</dependency> | ||||
| 		<dependency> | ||||
| 			<groupId>org.springframework</groupId> | ||||
| 			<artifactId>spring-webflux</artifactId> | ||||
| 			<optional>true</optional> | ||||
| 		</dependency> | ||||
| 		<dependency> | ||||
| 			<groupId>org.yaml</groupId> | ||||
| 			<artifactId>snakeyaml</artifactId> | ||||
|  |  | |||
|  | @ -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( | ||||
|  |  | |||
|  | @ -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; | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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 { | ||||
| } | ||||
|  | @ -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( | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue