diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml
index ddac4abdca..a3be4d7b90 100755
--- a/spring-boot-autoconfigure/pom.xml
+++ b/spring-boot-autoconfigure/pom.xml
@@ -354,6 +354,11 @@
spring-websocket
true
+
+ org.springframework
+ spring-webflux
+ true
+
org.springframework
spring-webmvc
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webflux/WebFluxAnnotationAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webflux/WebFluxAnnotationAutoConfiguration.java
new file mode 100644
index 0000000000..e669176a87
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webflux/WebFluxAnnotationAutoConfiguration.java
@@ -0,0 +1,263 @@
+/*
+ * 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.autoconfigure.webflux;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.AutoConfigureOrder;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
+import org.springframework.boot.autoconfigure.web.ResourceProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.convert.converter.GenericConverter;
+import org.springframework.format.Formatter;
+import org.springframework.format.FormatterRegistry;
+import org.springframework.http.CacheControl;
+import org.springframework.http.server.reactive.HttpHandler;
+import org.springframework.web.reactive.DispatcherHandler;
+import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration;
+import org.springframework.web.reactive.config.EnableWebFlux;
+import org.springframework.web.reactive.config.ResourceChainRegistration;
+import org.springframework.web.reactive.config.ResourceHandlerRegistration;
+import org.springframework.web.reactive.config.ResourceHandlerRegistry;
+import org.springframework.web.reactive.config.ViewResolverRegistry;
+import org.springframework.web.reactive.config.WebFluxConfigurationSupport;
+import org.springframework.web.reactive.config.WebFluxConfigurer;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.resource.AppCacheManifestTransformer;
+import org.springframework.web.reactive.resource.GzipResourceResolver;
+import org.springframework.web.reactive.resource.ResourceResolver;
+import org.springframework.web.reactive.resource.VersionResourceResolver;
+import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
+import org.springframework.web.reactive.result.view.ViewResolver;
+import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
+
+/**
+ * {@link EnableAutoConfiguration Auto-configuration} for {@link EnableWebFlux WebFlux}.
+ *
+ * @author Brian Clozel
+ * @author Rob Winch
+ */
+@Configuration
+@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
+@ConditionalOnClass({DispatcherHandler.class, HttpHandler.class})
+@ConditionalOnMissingBean({RouterFunction.class, HttpHandler.class})
+@AutoConfigureAfter(ReactiveWebServerAutoConfiguration.class)
+@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
+public class WebFluxAnnotationAutoConfiguration {
+
+ @Configuration
+ @ConditionalOnMissingBean(WebFluxConfigurationSupport.class)
+ @EnableConfigurationProperties({ResourceProperties.class, WebFluxProperties.class})
+ @Import(DelegatingWebFluxConfiguration.class)
+ public static class WebFluxConfig implements WebFluxConfigurer {
+
+ private static final Log logger = LogFactory.getLog(WebFluxConfig.class);
+
+ private final ResourceProperties resourceProperties;
+
+ private final WebFluxProperties webFluxProperties;
+
+ private final ListableBeanFactory beanFactory;
+
+ private final List argumentResolvers;
+
+ private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
+
+ private final List viewResolvers;
+
+
+ public WebFluxConfig(ResourceProperties resourceProperties,
+ WebFluxProperties webFluxProperties, ListableBeanFactory beanFactory,
+ ObjectProvider> resolvers,
+ ObjectProvider resourceHandlerRegistrationCustomizer,
+ ObjectProvider> viewResolvers) {
+ this.resourceProperties = resourceProperties;
+ this.webFluxProperties = webFluxProperties;
+ this.beanFactory = beanFactory;
+ this.argumentResolvers = resolvers.getIfAvailable();
+ this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizer.getIfAvailable();
+ this.viewResolvers = viewResolvers.getIfAvailable();
+ }
+
+ @Override
+ public void addArgumentResolvers(List resolvers) {
+ if (this.argumentResolvers != null) {
+ resolvers.addAll(this.argumentResolvers);
+ }
+ }
+
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ if (!this.resourceProperties.isAddMappings()) {
+ logger.debug("Default resource handling disabled");
+ return;
+ }
+ Integer cachePeriod = this.resourceProperties.getCachePeriod();
+ if (!registry.hasMappingForPattern("/webjars/**")) {
+ ResourceHandlerRegistration registration = registry
+ .addResourceHandler("/webjars/**")
+ .addResourceLocations("classpath:/META-INF/resources/webjars/");
+ if (cachePeriod != null) {
+ registration.setCacheControl(CacheControl.maxAge(cachePeriod, TimeUnit.SECONDS));
+ }
+ customizeResourceHandlerRegistration(registration);
+ }
+ String staticPathPattern = this.webFluxProperties.getStaticPathPattern();
+ if (!registry.hasMappingForPattern(staticPathPattern)) {
+ ResourceHandlerRegistration registration = registry.addResourceHandler(staticPathPattern)
+ .addResourceLocations(this.resourceProperties.getStaticLocations());
+ if (cachePeriod != null) {
+ registration.setCacheControl(CacheControl.maxAge(cachePeriod, TimeUnit.SECONDS));
+ }
+ customizeResourceHandlerRegistration(registration);
+ }
+ }
+
+ @Override
+ public void configureViewResolvers(ViewResolverRegistry registry) {
+ if (this.viewResolvers != null) {
+ AnnotationAwareOrderComparator.sort(this.viewResolvers);
+ this.viewResolvers.forEach(resolver -> registry.viewResolver(resolver));
+ }
+ }
+
+ @Override
+ public void addFormatters(final FormatterRegistry registry) {
+ for (Converter, ?> converter : getBeansOfType(Converter.class)) {
+ registry.addConverter(converter);
+ }
+ for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
+ registry.addConverter(converter);
+ }
+ for (Formatter> formatter : getBeansOfType(Formatter.class)) {
+ registry.addFormatter(formatter);
+ }
+ }
+
+ private Collection getBeansOfType(Class type) {
+ return this.beanFactory.getBeansOfType(type).values();
+ }
+
+ private void customizeResourceHandlerRegistration(
+ ResourceHandlerRegistration registration) {
+ if (this.resourceHandlerRegistrationCustomizer != null) {
+ this.resourceHandlerRegistrationCustomizer.customize(registration);
+ }
+
+ }
+ }
+
+ @Configuration
+ @Import(WebFluxConfig.class)
+ public static class WebHttpHandlerConfiguration implements ApplicationContextAware {
+
+ private ApplicationContext applicationContext;
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext)
+ throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+
+ @Bean
+ public HttpHandler httpHandler() {
+ return WebHttpHandlerBuilder.applicationContext(this.applicationContext).build();
+ }
+ }
+
+ @Configuration
+ @ConditionalOnEnabledResourceChain
+ static class ResourceChainCustomizerConfiguration {
+
+ @Bean
+ public ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer() {
+ return new ResourceChainResourceHandlerRegistrationCustomizer();
+ }
+ }
+
+ interface ResourceHandlerRegistrationCustomizer {
+
+ void customize(ResourceHandlerRegistration registration);
+
+ }
+
+ private static class ResourceChainResourceHandlerRegistrationCustomizer
+ implements ResourceHandlerRegistrationCustomizer {
+
+ @Autowired
+ private ResourceProperties resourceProperties = new ResourceProperties();
+
+ @Override
+ public void customize(ResourceHandlerRegistration registration) {
+ ResourceProperties.Chain properties = this.resourceProperties.getChain();
+ configureResourceChain(properties,
+ registration.resourceChain(properties.isCache()));
+ }
+
+ private void configureResourceChain(ResourceProperties.Chain properties,
+ ResourceChainRegistration chain) {
+ ResourceProperties.Strategy strategy = properties.getStrategy();
+ if (strategy.getFixed().isEnabled() || strategy.getContent().isEnabled()) {
+ chain.addResolver(getVersionResourceResolver(strategy));
+ }
+ if (properties.isGzipped()) {
+ chain.addResolver(new GzipResourceResolver());
+ }
+ if (properties.isHtmlApplicationCache()) {
+ chain.addTransformer(new AppCacheManifestTransformer());
+ }
+ }
+
+ private ResourceResolver getVersionResourceResolver(
+ ResourceProperties.Strategy properties) {
+ VersionResourceResolver resolver = new VersionResourceResolver();
+ if (properties.getFixed().isEnabled()) {
+ String version = properties.getFixed().getVersion();
+ String[] paths = properties.getFixed().getPaths();
+ resolver.addFixedVersionStrategy(version, paths);
+ }
+ if (properties.getContent().isEnabled()) {
+ String[] paths = properties.getContent().getPaths();
+ resolver.addContentVersionStrategy(paths);
+ }
+ return resolver;
+ }
+
+ }
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webflux/WebFluxFunctionalAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webflux/WebFluxFunctionalAutoConfiguration.java
new file mode 100644
index 0000000000..5fa8df8080
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webflux/WebFluxFunctionalAutoConfiguration.java
@@ -0,0 +1,113 @@
+/*
+ * 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.autoconfigure.webflux;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.AutoConfigureOrder;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.http.codec.HttpMessageReader;
+import org.springframework.http.codec.HttpMessageWriter;
+import org.springframework.http.server.reactive.HttpHandler;
+import org.springframework.web.reactive.DispatcherHandler;
+import org.springframework.web.reactive.function.server.HandlerStrategies;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+import org.springframework.web.reactive.result.view.ViewResolver;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebHandler;
+import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
+import org.springframework.web.server.session.WebSessionManager;
+
+/**
+ * {@link EnableAutoConfiguration Auto-configuration} for Functional WebFlux.
+ *
+ * @author Brian Clozel
+ */
+@Configuration
+@ConditionalOnClass({DispatcherHandler.class, HttpHandler.class})
+@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
+@ConditionalOnBean(RouterFunction.class)
+@ConditionalOnMissingBean(HttpHandler.class)
+@AutoConfigureAfter({ReactiveWebServerAutoConfiguration.class})
+@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
+public class WebFluxFunctionalAutoConfiguration {
+
+ @Configuration
+ public static class WebFluxFunctionalConfig {
+
+ private final List webFilters;
+
+ private final WebSessionManager webSessionManager;
+
+ private final List messageReaders;
+
+ private final List messageWriters;
+
+ private final List viewResolvers;
+
+ public WebFluxFunctionalConfig(ObjectProvider> webFilters,
+ ObjectProvider webSessionManager,
+ ObjectProvider> messageReaders,
+ ObjectProvider> messageWriters,
+ ObjectProvider> viewResolvers) {
+ this.webFilters = webFilters.getIfAvailable();
+ if (this.webFilters != null) {
+ AnnotationAwareOrderComparator.sort(this.webFilters);
+ }
+ this.webSessionManager = webSessionManager.getIfAvailable();
+ this.messageReaders = messageReaders.getIfAvailable();
+ this.messageWriters = messageWriters.getIfAvailable();
+ this.viewResolvers = viewResolvers.getIfAvailable();
+ }
+
+ @Bean
+ public HttpHandler httpHandler(List routerFunctions) {
+ Collections.sort(routerFunctions, new AnnotationAwareOrderComparator());
+ RouterFunction routerFunction = routerFunctions.stream().reduce(RouterFunction::and).get();
+ HandlerStrategies.Builder strategiesBuilder = HandlerStrategies.builder();
+ if (this.messageReaders != null) {
+ this.messageReaders.forEach(reader -> strategiesBuilder.messageReader(reader));
+ }
+ if (this.messageWriters != null) {
+ this.messageWriters.forEach(writer -> strategiesBuilder.messageWriter(writer));
+ }
+ if (this.viewResolvers != null) {
+ this.viewResolvers.forEach(viewResolver -> strategiesBuilder.viewResolver(viewResolver));
+ }
+ WebHandler webHandler = RouterFunctions.toHttpHandler(routerFunction, strategiesBuilder.build());
+ WebHttpHandlerBuilder builder = WebHttpHandlerBuilder
+ .webHandler(webHandler)
+ .sessionManager(this.webSessionManager);
+ if (this.webFilters != null) {
+ builder.filters(this.webFilters.toArray(new WebFilter[this.webFilters.size()]));
+ }
+ return builder.build();
+ }
+ }
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webflux/WebFluxProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webflux/WebFluxProperties.java
new file mode 100644
index 0000000000..cc39c80b32
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webflux/WebFluxProperties.java
@@ -0,0 +1,42 @@
+/*
+ * 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.autoconfigure.webflux;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * {@link ConfigurationProperties properties} for Spring WebFlux.
+ *
+ * @author Brian Clozel
+ * @since 2.0.0
+ */
+@ConfigurationProperties(prefix = "spring.webflux")
+public class WebFluxProperties {
+
+ /**
+ * Path pattern used for static resources.
+ */
+ private String staticPathPattern = "/**";
+
+ public String getStaticPathPattern() {
+ return this.staticPathPattern;
+ }
+
+ public void setStaticPathPattern(String staticPathPattern) {
+ this.staticPathPattern = staticPathPattern;
+ }
+}
diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
index 2dcde3f4fc..0bcb57b33b 100644
--- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
+++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
@@ -113,6 +113,8 @@ org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.webflux.ReactiveWebServerAutoConfiguration,\
+org.springframework.boot.autoconfigure.webflux.WebFluxAnnotationAutoConfiguration,\
+org.springframework.boot.autoconfigure.webflux.WebFluxFunctionalAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/webflux/WebFluxAnnotationAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/webflux/WebFluxAnnotationAutoConfigurationTests.java
new file mode 100644
index 0000000000..01394b5da6
--- /dev/null
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/webflux/WebFluxAnnotationAutoConfigurationTests.java
@@ -0,0 +1,257 @@
+/*
+ * 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.autoconfigure.webflux;
+
+import org.junit.Test;
+
+import org.springframework.boot.context.embedded.ReactiveWebApplicationContext;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.test.util.EnvironmentTestUtils;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.http.server.reactive.HttpHandler;
+import org.springframework.web.reactive.HandlerMapping;
+import org.springframework.web.reactive.accept.CompositeContentTypeResolver;
+import org.springframework.web.reactive.config.WebFluxConfigurationSupport;
+import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
+import org.springframework.web.reactive.resource.CachingResourceResolver;
+import org.springframework.web.reactive.resource.CachingResourceTransformer;
+import org.springframework.web.reactive.resource.PathResourceResolver;
+import org.springframework.web.reactive.resource.ResourceWebHandler;
+import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
+import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
+import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
+import org.springframework.web.reactive.result.view.ViewResolutionResultHandler;
+import org.springframework.web.reactive.result.view.ViewResolver;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebHandler;
+import org.springframework.web.server.adapter.HttpWebHandlerAdapter;
+import org.springframework.web.server.handler.FilteringWebHandler;
+import org.springframework.web.server.handler.WebHandlerDecorator;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link WebFluxAnnotationAutoConfiguration}.
+ *
+ * @author Brian Clozel
+ */
+public class WebFluxAnnotationAutoConfigurationTests {
+
+ private ReactiveWebApplicationContext context;
+
+ @Test
+ public void shouldNotProcessIfExistingHttpHandler() throws Exception {
+ load(CustomHttpHandler.class);
+
+ assertThat(this.context.getBeansOfType(RequestMappingHandlerMapping.class).size()).isEqualTo(0);
+ assertThat(this.context.getBeansOfType(RequestMappingHandlerAdapter.class).size()).isEqualTo(0);
+ assertThat(this.context.getBeansOfType(HttpWebHandlerAdapter.class).size()).isEqualTo(0);
+ }
+
+
+ @Test
+ public void shouldNotProcessIfExistingWebReactiveConfiguration() throws Exception {
+ load(WebFluxConfigurationSupport.class);
+
+ assertThat(this.context.getBeansOfType(RequestMappingHandlerMapping.class).size()).isEqualTo(1);
+ assertThat(this.context.getBeansOfType(RequestMappingHandlerAdapter.class).size()).isEqualTo(1);
+ }
+
+ @Test
+ public void shouldCreateDefaultBeans() throws Exception {
+ load(BaseConfiguration.class);
+
+ assertThat(this.context.getBeansOfType(RequestMappingHandlerMapping.class).size()).isEqualTo(1);
+ assertThat(this.context.getBeansOfType(RequestMappingHandlerAdapter.class).size()).isEqualTo(1);
+ assertThat(this.context.getBeansOfType(CompositeContentTypeResolver.class).size()).isEqualTo(1);
+ assertThat(this.context.getBean("resourceHandlerMapping", HandlerMapping.class)).isNotNull();
+ }
+
+ @Test
+ public void shouldRegisterCustomHandlerMethodArgumentResolver() throws Exception {
+ load(CustomArgumentResolvers.class);
+
+ RequestMappingHandlerAdapter adapter = this.context.getBean(RequestMappingHandlerAdapter.class);
+ assertThat(adapter.getArgumentResolvers())
+ .contains(this.context.getBean("firstResolver", HandlerMethodArgumentResolver.class),
+ this.context.getBean("secondResolver", HandlerMethodArgumentResolver.class));
+ }
+
+ @Test
+ public void shouldRegisterCustomWebFilters() throws Exception {
+ load(CustomWebFilters.class);
+
+ HttpHandler handler = this.context.getBean(HttpHandler.class);
+ assertThat(handler).isInstanceOf(WebHandler.class);
+ WebHandler webHandler = (WebHandler) handler;
+ while (webHandler instanceof WebHandlerDecorator) {
+ if (webHandler instanceof FilteringWebHandler) {
+ FilteringWebHandler filteringWebHandler = (FilteringWebHandler) webHandler;
+ assertThat(filteringWebHandler.getFilters()).containsExactly(
+ this.context.getBean("firstWebFilter", WebFilter.class),
+ this.context.getBean("aWebFilter", WebFilter.class),
+ this.context.getBean("lastWebFilter", WebFilter.class));
+ return;
+ }
+ webHandler = ((WebHandlerDecorator) webHandler).getDelegate();
+ }
+ fail("Did not find any FilteringWebHandler");
+ }
+
+ @Test
+ public void shouldRegisterResourceHandlerMapping() throws Exception {
+ load(BaseConfiguration.class);
+
+ SimpleUrlHandlerMapping hm = this.context.getBean("resourceHandlerMapping", SimpleUrlHandlerMapping.class);
+ assertThat(hm.getUrlMap().get("/**")).isInstanceOf(ResourceWebHandler.class);
+ ResourceWebHandler staticHandler = (ResourceWebHandler) hm.getUrlMap().get("/**");
+ assertThat(staticHandler.getLocations()).hasSize(5);
+
+ assertThat(hm.getUrlMap().get("/webjars/**")).isInstanceOf(ResourceWebHandler.class);
+ ResourceWebHandler webjarsHandler = (ResourceWebHandler) hm.getUrlMap().get("/webjars/**");
+ assertThat(webjarsHandler.getLocations()).hasSize(1);
+ assertThat(webjarsHandler.getLocations().get(0))
+ .isEqualTo(new ClassPathResource("/META-INF/resources/webjars/"));
+ }
+
+ @Test
+ public void shouldMapResourcesToCustomPath() throws Exception {
+ load(BaseConfiguration.class, "spring.webflux.static-path-pattern:/static/**");
+ SimpleUrlHandlerMapping hm = this.context.getBean("resourceHandlerMapping", SimpleUrlHandlerMapping.class);
+ assertThat(hm.getUrlMap().get("/static/**")).isInstanceOf(ResourceWebHandler.class);
+ ResourceWebHandler staticHandler = (ResourceWebHandler) hm.getUrlMap().get("/static/**");
+ assertThat(staticHandler.getLocations()).hasSize(5);
+ }
+
+ @Test
+ public void shouldNotMapResourcesWhenDisabled() throws Exception {
+ load(BaseConfiguration.class, "spring.resources.add-mappings:false");
+ assertThat(this.context.getBean("resourceHandlerMapping")).isNotInstanceOf(SimpleUrlHandlerMapping.class);
+ }
+
+ @Test
+ public void resourceHandlerChainEnabled() throws Exception {
+ load(BaseConfiguration.class, "spring.resources.chain.enabled:true");
+ SimpleUrlHandlerMapping hm = this.context.getBean("resourceHandlerMapping", SimpleUrlHandlerMapping.class);
+ assertThat(hm.getUrlMap().get("/**")).isInstanceOf(ResourceWebHandler.class);
+ ResourceWebHandler staticHandler = (ResourceWebHandler) hm.getUrlMap().get("/**");
+ assertThat(staticHandler.getResourceResolvers()).extractingResultOf("getClass")
+ .containsOnly(CachingResourceResolver.class, PathResourceResolver.class);
+ assertThat(staticHandler.getResourceTransformers()).extractingResultOf("getClass")
+ .containsOnly(CachingResourceTransformer.class);
+ }
+
+ @Test
+ public void shouldRegisterViewResolvers() throws Exception {
+ load(ViewResolvers.class);
+ ViewResolutionResultHandler resultHandler = this.context.getBean(ViewResolutionResultHandler.class);
+ assertThat(resultHandler.getViewResolvers()).containsExactly(
+ this.context.getBean("aViewResolver", ViewResolver.class),
+ this.context.getBean("anotherViewResolver", ViewResolver.class)
+ );
+ }
+
+ private void load(Class> config, String... environment) {
+ this.context = new ReactiveWebApplicationContext();
+ EnvironmentTestUtils.addEnvironment(this.context, environment);
+ this.context.register(config);
+ if (!config.equals(BaseConfiguration.class)) {
+ this.context.register(BaseConfiguration.class);
+ }
+ this.context.refresh();
+ }
+
+
+ @Configuration
+ protected static class CustomWebFilters {
+
+ @Bean
+ public WebFilter aWebFilter() {
+ return mock(WebFilter.class);
+ }
+
+ @Bean
+ @Order(Ordered.LOWEST_PRECEDENCE)
+ public WebFilter lastWebFilter() {
+ return mock(WebFilter.class);
+ }
+
+ @Bean
+ @Order(Ordered.HIGHEST_PRECEDENCE)
+ public WebFilter firstWebFilter() {
+ return mock(WebFilter.class);
+ }
+ }
+
+ @Configuration
+ protected static class CustomArgumentResolvers {
+
+ @Bean
+ public HandlerMethodArgumentResolver firstResolver() {
+ return mock(HandlerMethodArgumentResolver.class);
+ }
+
+ @Bean
+ public HandlerMethodArgumentResolver secondResolver() {
+ return mock(HandlerMethodArgumentResolver.class);
+ }
+
+ }
+
+ @Configuration
+ protected static class ViewResolvers {
+
+ @Bean
+ @Order(Ordered.HIGHEST_PRECEDENCE)
+ public ViewResolver aViewResolver() {
+ return mock(ViewResolver.class);
+ }
+
+ @Bean
+ public ViewResolver anotherViewResolver() {
+ return mock(ViewResolver.class);
+ }
+ }
+
+ @Configuration
+ @Import({WebFluxAnnotationAutoConfiguration.class})
+ @EnableConfigurationProperties(WebFluxProperties.class)
+ protected static class BaseConfiguration {
+
+ @Bean
+ public MockReactiveWebServerFactory mockReactiveWebServerFactory() {
+ return new MockReactiveWebServerFactory();
+ }
+
+ }
+
+ @Configuration
+ protected static class CustomHttpHandler {
+
+ @Bean
+ public HttpHandler httpHandler() {
+ return (serverHttpRequest, serverHttpResponse) -> null;
+ }
+ }
+}
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/webflux/WebFluxFunctionalAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/webflux/WebFluxFunctionalAutoConfigurationTests.java
new file mode 100644
index 0000000000..0099c93fc9
--- /dev/null
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/webflux/WebFluxFunctionalAutoConfigurationTests.java
@@ -0,0 +1,153 @@
+/*
+ * 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.autoconfigure.webflux;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import org.springframework.boot.context.embedded.ReactiveWebApplicationContext;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.test.util.EnvironmentTestUtils;
+import org.springframework.context.ApplicationContextException;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.server.reactive.HttpHandler;
+import org.springframework.web.reactive.function.server.RequestPredicates;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebHandler;
+import org.springframework.web.server.adapter.HttpWebHandlerAdapter;
+import org.springframework.web.server.handler.FilteringWebHandler;
+import org.springframework.web.server.handler.WebHandlerDecorator;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+
+/**
+ * Tests for {@link WebFluxFunctionalAutoConfiguration}.
+ *
+ * @author Brian Clozel
+ */
+public class WebFluxFunctionalAutoConfigurationTests {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ private ReactiveWebApplicationContext context;
+
+ @Test
+ public void shouldNotProcessIfExistingHttpHandler() throws Exception {
+ load(CustomHttpHandler.class);
+ assertThat(this.context.getBeansOfType(HttpWebHandlerAdapter.class).size()).isEqualTo(0);
+ }
+
+ @Test
+ public void shouldFailIfNoHttpHandler() throws Exception {
+ this.thrown.expect(ApplicationContextException.class);
+ this.thrown.expectMessage("Unable to start ReactiveWebApplicationContext due to missing HttpHandler bean.");
+ load(BaseConfiguration.class);
+ }
+
+ @Test
+ public void shouldConfigureHttpHandler() {
+ load(FunctionalConfig.class);
+ assertThat(this.context.getBeansOfType(HttpHandler.class).size()).isEqualTo(1);
+ }
+
+ @Test
+ public void shouldConfigureWebFilters() {
+ load(FunctionalConfigWithWebFilters.class);
+ assertThat(this.context.getBeansOfType(HttpHandler.class).size()).isEqualTo(1);
+ HttpHandler handler = this.context.getBean(HttpHandler.class);
+ assertThat(handler).isInstanceOf(WebHandler.class);
+ WebHandler webHandler = (WebHandler) handler;
+ while (webHandler instanceof WebHandlerDecorator) {
+ if (webHandler instanceof FilteringWebHandler) {
+ FilteringWebHandler filteringWebHandler = (FilteringWebHandler) webHandler;
+ assertThat(filteringWebHandler.getFilters()).containsExactly(
+ this.context.getBean("customWebFilter", WebFilter.class));
+ return;
+ }
+ webHandler = ((WebHandlerDecorator) webHandler).getDelegate();
+ }
+ fail("Did not find any FilteringWebHandler");
+ }
+
+
+ private void load(Class> config, String... environment) {
+ this.context = new ReactiveWebApplicationContext();
+ EnvironmentTestUtils.addEnvironment(this.context, environment);
+ this.context.register(config);
+ if (!config.equals(BaseConfiguration.class)) {
+ this.context.register(BaseConfiguration.class);
+ }
+ this.context.refresh();
+ }
+
+
+ @Configuration
+ @Import({WebFluxFunctionalAutoConfiguration.class})
+ @EnableConfigurationProperties(WebFluxProperties.class)
+ protected static class BaseConfiguration {
+
+ @Bean
+ public MockReactiveWebServerFactory mockReactiveWebServerFactory() {
+ return new MockReactiveWebServerFactory();
+ }
+ }
+
+ @Configuration
+ protected static class FunctionalConfig {
+
+ @Bean
+ public RouterFunction routerFunction() {
+ return RouterFunctions.route(RequestPredicates.GET("/test"), serverRequest -> null);
+ }
+ }
+
+ @Configuration
+ protected static class FunctionalConfigWithWebFilters {
+
+ @Bean
+ public RouterFunction routerFunction() {
+ return RouterFunctions.route(RequestPredicates.GET("/test"), serverRequest -> null);
+ }
+
+ @Bean
+ public WebFilter customWebFilter() {
+ return (serverWebExchange, webFilterChain) -> null;
+ }
+ }
+
+ @Configuration
+ protected static class CustomHttpHandler {
+
+ @Bean
+ public HttpHandler httpHandler() {
+ return (serverHttpRequest, serverHttpResponse) -> null;
+ }
+
+ @Bean
+ public RouterFunction routerFunction() {
+ return RouterFunctions.route(RequestPredicates.GET("/test"), serverRequest -> null);
+ }
+ }
+
+}