diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/PathMatchConfigurer.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/PathMatchConfigurer.java
new file mode 100644
index 00000000000..c45565372ae
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/PathMatchConfigurer.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.config;
+
+import org.springframework.util.PathMatcher;
+import org.springframework.web.util.HttpRequestPathHelper;
+
+/**
+ * Assist with configuring {@code HandlerMapping}'s with path matching options.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class PathMatchConfigurer {
+
+ private Boolean suffixPatternMatch;
+
+ private Boolean trailingSlashMatch;
+
+ private Boolean registeredSuffixPatternMatch;
+
+ private HttpRequestPathHelper pathHelper;
+
+ private PathMatcher pathMatcher;
+
+
+ /**
+ * Whether to use suffix pattern match (".*") when matching patterns to
+ * requests. If enabled a method mapped to "/users" also matches to "/users.*".
+ *
By default this is set to {@code true}.
+ * @see #registeredSuffixPatternMatch
+ */
+ public PathMatchConfigurer setUseSuffixPatternMatch(Boolean suffixPatternMatch) {
+ this.suffixPatternMatch = suffixPatternMatch;
+ return this;
+ }
+
+ /**
+ * Whether to match to URLs irrespective of the presence of a trailing slash.
+ * If enabled a method mapped to "/users" also matches to "/users/".
+ *
The default value is {@code true}.
+ */
+ public PathMatchConfigurer setUseTrailingSlashMatch(Boolean trailingSlashMatch) {
+ this.trailingSlashMatch = trailingSlashMatch;
+ return this;
+ }
+
+ /**
+ * Whether suffix pattern matching should work only against path extensions
+ * that are explicitly registered. This is generally recommended to reduce
+ * ambiguity and to avoid issues such as when a "." (dot) appears in the path
+ * for other reasons.
+ *
By default this is set to "true".
+ */
+ public PathMatchConfigurer setUseRegisteredSuffixPatternMatch(Boolean registeredSuffixPatternMatch) {
+ this.registeredSuffixPatternMatch = registeredSuffixPatternMatch;
+ return this;
+ }
+
+ /**
+ * Set a {@code HttpRequestPathHelper} for the resolution of lookup paths.
+ *
Default is {@code HttpRequestPathHelper}.
+ */
+ public PathMatchConfigurer setPathHelper(HttpRequestPathHelper pathHelper) {
+ this.pathHelper = pathHelper;
+ return this;
+ }
+
+ /**
+ * Set the PathMatcher for matching URL paths against registered URL patterns.
+ *
Default is {@link org.springframework.util.AntPathMatcher AntPathMatcher}.
+ */
+ public PathMatchConfigurer setPathMatcher(PathMatcher pathMatcher) {
+ this.pathMatcher = pathMatcher;
+ return this;
+ }
+
+ protected Boolean isUseSuffixPatternMatch() {
+ return this.suffixPatternMatch;
+ }
+
+ protected Boolean isUseTrailingSlashMatch() {
+ return this.trailingSlashMatch;
+ }
+
+ protected Boolean isUseRegisteredSuffixPatternMatch() {
+ return this.registeredSuffixPatternMatch;
+ }
+
+ protected HttpRequestPathHelper getPathHelper() {
+ return this.pathHelper;
+ }
+
+ protected PathMatcher getPathMatcher() {
+ return this.pathMatcher;
+ }
+
+}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/UrlBasedViewResolverRegistration.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/UrlBasedViewResolverRegistration.java
new file mode 100644
index 00000000000..422449d7537
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/UrlBasedViewResolverRegistration.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.config;
+
+import org.springframework.util.Assert;
+import org.springframework.web.reactive.result.view.UrlBasedViewResolver;
+
+/**
+ * Assist with configuring properties of a {@link UrlBasedViewResolver}.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class UrlBasedViewResolverRegistration {
+
+ private final UrlBasedViewResolver viewResolver;
+
+
+ public UrlBasedViewResolverRegistration(UrlBasedViewResolver viewResolver) {
+ Assert.notNull(viewResolver);
+ this.viewResolver = viewResolver;
+ }
+
+
+ /**
+ * Set the prefix that gets prepended to view names when building a URL.
+ * @see UrlBasedViewResolver#setPrefix
+ */
+ public UrlBasedViewResolverRegistration prefix(String prefix) {
+ this.viewResolver.setPrefix(prefix);
+ return this;
+ }
+
+ /**
+ * Set the suffix that gets appended to view names when building a URL.
+ * @see UrlBasedViewResolver#setSuffix
+ */
+ public UrlBasedViewResolverRegistration suffix(String suffix) {
+ this.viewResolver.setSuffix(suffix);
+ return this;
+ }
+
+ /**
+ * Set the view class that should be used to create views.
+ * @see UrlBasedViewResolver#setViewClass
+ */
+ public UrlBasedViewResolverRegistration viewClass(Class> viewClass) {
+ this.viewResolver.setViewClass(viewClass);
+ return this;
+ }
+
+ /**
+ * Set the view names (or name patterns) that can be handled by this view
+ * resolver. View names can contain simple wildcards such that 'my*', '*Report'
+ * and '*Repo*' will all match the view name 'myReport'.
+ * @see UrlBasedViewResolver#setViewNames
+ */
+ public UrlBasedViewResolverRegistration viewNames(String... viewNames) {
+ this.viewResolver.setViewNames(viewNames);
+ return this;
+ }
+
+ protected UrlBasedViewResolver getViewResolver() {
+ return this.viewResolver;
+ }
+
+}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/ViewResolverRegistry.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/ViewResolverRegistry.java
new file mode 100644
index 00000000000..ce5989002d8
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/ViewResolverRegistry.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.config;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.BeanInitializationException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.Ordered;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+import org.springframework.web.reactive.result.view.UrlBasedViewResolver;
+import org.springframework.web.reactive.result.view.View;
+import org.springframework.web.reactive.result.view.ViewResolver;
+import org.springframework.web.reactive.result.view.freemarker.FreeMarkerConfigurer;
+import org.springframework.web.reactive.result.view.freemarker.FreeMarkerViewResolver;
+
+
+/**
+ * Assist with the configuration of a chain of {@link ViewResolver}'s supporting
+ * different template mechanisms.
+ *
+ *
In addition, you can also configure {@link #defaultViews(View...)
+ * defaultViews} for rendering according to the requested content type, e.g.
+ * JSON, XML, etc.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class ViewResolverRegistry {
+
+ private final List viewResolvers = new ArrayList<>(4);
+
+ private final List defaultViews = new ArrayList<>(4);
+
+ private Integer order;
+
+ private final ApplicationContext applicationContext;
+
+
+ public ViewResolverRegistry(ApplicationContext applicationContext) {
+ Assert.notNull(applicationContext);
+ this.applicationContext = applicationContext;
+ }
+
+
+ /**
+ * Register a {@code FreeMarkerViewResolver} with a ".ftl" suffix.
+ * Note that you must also configure FreeMarker by
+ * adding a {@link FreeMarkerConfigurer} bean.
+ */
+ public UrlBasedViewResolverRegistration freeMarker() {
+ if (this.applicationContext != null && !hasBeanOfType(FreeMarkerConfigurer.class)) {
+ throw new BeanInitializationException("In addition to a FreeMarker view resolver " +
+ "there must also be a single FreeMarkerConfig bean in this web application context " +
+ "(or its parent): FreeMarkerConfigurer is the usual implementation. " +
+ "This bean may be given any name.");
+ }
+ FreeMarkerRegistration registration = new FreeMarkerRegistration();
+ UrlBasedViewResolver resolver = registration.getViewResolver();
+ resolver.setApplicationContext(this.applicationContext);
+ this.viewResolvers.add(resolver);
+ return registration;
+ }
+
+ protected boolean hasBeanOfType(Class> beanType) {
+ return !ObjectUtils.isEmpty(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
+ this.applicationContext, beanType, false, false));
+ }
+
+ /**
+ * Register a {@link ViewResolver} bean instance. This may be useful to
+ * configure a 3rd party resolver implementation or as an alternative to
+ * other registration methods in this class when they don't expose some
+ * more advanced property that needs to be set.
+ */
+ public void viewResolver(ViewResolver viewResolver) {
+ this.viewResolvers.add(viewResolver);
+ }
+
+ /**
+ * Set default views associated with any view name and selected based on the
+ * best match for the requested content type.
+ *
Use {@link org.springframework.web.reactive.result.view.HttpMessageConverterView
+ * HttpMessageConverterView} to adapt and use any existing
+ * {@code HttpMessageConverter} (e.g. JSON, XML) as a {@code View}.
+ */
+ public void defaultViews(View... defaultViews) {
+ this.defaultViews.addAll(Arrays.asList(defaultViews));
+ }
+
+ /**
+ * Whether any view resolvers have been registered.
+ */
+ public boolean hasRegistrations() {
+ return (!this.viewResolvers.isEmpty());
+ }
+
+ /**
+ * Set the order for the
+ * {@link org.springframework.web.reactive.result.view.ViewResolutionResultHandler
+ * ViewResolutionResultHandler}.
+ *
By default this property is not set, which means the result handler is
+ * ordered at {@link Ordered#LOWEST_PRECEDENCE}.
+ */
+ public void order(int order) {
+ this.order = order;
+ }
+
+ protected int getOrder() {
+ return (this.order != null ? this.order : Ordered.LOWEST_PRECEDENCE);
+ }
+
+ protected List getViewResolvers() {
+ return this.viewResolvers;
+ }
+
+ protected List getDefaultViews() {
+ return this.defaultViews;
+ }
+
+
+ private static class FreeMarkerRegistration extends UrlBasedViewResolverRegistration {
+
+ public FreeMarkerRegistration() {
+ super(new FreeMarkerViewResolver());
+ getViewResolver().setSuffix(".ftl");
+ }
+ }
+
+}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java
new file mode 100644
index 00000000000..9f4b0e495a4
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.config;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import reactor.core.converter.DependencyUtils;
+
+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.core.codec.Decoder;
+import org.springframework.core.codec.Encoder;
+import org.springframework.core.codec.support.ByteBufferDecoder;
+import org.springframework.core.codec.support.ByteBufferEncoder;
+import org.springframework.core.codec.support.JacksonJsonDecoder;
+import org.springframework.core.codec.support.JacksonJsonEncoder;
+import org.springframework.core.codec.support.Jaxb2Decoder;
+import org.springframework.core.codec.support.Jaxb2Encoder;
+import org.springframework.core.codec.support.JsonObjectDecoder;
+import org.springframework.core.codec.support.StringDecoder;
+import org.springframework.core.codec.support.StringEncoder;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.convert.converter.ConverterRegistry;
+import org.springframework.core.convert.support.GenericConversionService;
+import org.springframework.core.convert.support.ReactiveStreamsToCompletableFutureConverter;
+import org.springframework.core.convert.support.ReactiveStreamsToRxJava1Converter;
+import org.springframework.format.Formatter;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.reactive.CodecHttpMessageConverter;
+import org.springframework.http.converter.reactive.HttpMessageConverter;
+import org.springframework.http.converter.reactive.ResourceHttpMessageConverter;
+import org.springframework.util.ClassUtils;
+import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
+import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
+import org.springframework.web.reactive.result.SimpleHandlerAdapter;
+import org.springframework.web.reactive.result.SimpleResultHandler;
+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.method.annotation.ResponseBodyResultHandler;
+import org.springframework.web.reactive.result.view.ViewResolutionResultHandler;
+import org.springframework.web.reactive.result.view.ViewResolver;
+
+/**
+ * The main class for Spring Web Reactive configuration.
+ *
+ * Import directly or extend and override protected methods to customize.
+ *
+ * @author Rossen Stoyanchev
+ */
+@Configuration @SuppressWarnings("unused")
+public class WebReactiveConfiguration implements ApplicationContextAware {
+
+ private static final ClassLoader classLoader = WebReactiveConfiguration.class.getClassLoader();
+
+ private static final boolean jackson2Present =
+ ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
+ ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
+
+ private static final boolean jaxb2Present =
+ ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
+
+
+ private PathMatchConfigurer pathMatchConfigurer;
+
+ private List> messageConverters;
+
+ private ApplicationContext applicationContext;
+
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) {
+ this.applicationContext = applicationContext;
+ }
+
+
+ @Bean
+ public RequestMappingHandlerMapping requestMappingHandlerMapping() {
+ RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
+ mapping.setOrder(0);
+ mapping.setContentTypeResolver(mvcContentTypeResolver());
+
+ PathMatchConfigurer configurer = getPathMatchConfigurer();
+ if (configurer.isUseSuffixPatternMatch() != null) {
+ mapping.setUseSuffixPatternMatch(configurer.isUseSuffixPatternMatch());
+ }
+ if (configurer.isUseRegisteredSuffixPatternMatch() != null) {
+ mapping.setUseRegisteredSuffixPatternMatch(configurer.isUseRegisteredSuffixPatternMatch());
+ }
+ if (configurer.isUseTrailingSlashMatch() != null) {
+ mapping.setUseTrailingSlashMatch(configurer.isUseTrailingSlashMatch());
+ }
+ if (configurer.getPathMatcher() != null) {
+ mapping.setPathMatcher(configurer.getPathMatcher());
+ }
+ if (configurer.getPathHelper() != null) {
+ mapping.setPathHelper(configurer.getPathHelper());
+ }
+
+ return mapping;
+ }
+
+ /**
+ * Override to plug a sub-class of {@link RequestMappingHandlerMapping}.
+ */
+ protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
+ return new RequestMappingHandlerMapping();
+ }
+
+ @Bean
+ public RequestedContentTypeResolver mvcContentTypeResolver() {
+ RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder();
+ builder.mediaTypes(getDefaultMediaTypeMappings());
+ configureRequestedContentTypeResolver(builder);
+ return builder.build();
+ }
+
+ /**
+ * Override to configure media type mappings.
+ * @see RequestedContentTypeResolverBuilder#mediaTypes(Map)
+ */
+ protected Map getDefaultMediaTypeMappings() {
+ Map map = new HashMap<>();
+ if (jackson2Present) {
+ map.put("json", MediaType.APPLICATION_JSON);
+ }
+ return map;
+ }
+
+ /**
+ * Override to configure how the requested content type is resolved.
+ */
+ protected void configureRequestedContentTypeResolver(RequestedContentTypeResolverBuilder builder) {
+ }
+
+ /**
+ * Callback for building the {@link PathMatchConfigurer}. This method is
+ * final, use {@link #configurePathMatching} to customize path matching.
+ */
+ protected final PathMatchConfigurer getPathMatchConfigurer() {
+ if (this.pathMatchConfigurer == null) {
+ this.pathMatchConfigurer = new PathMatchConfigurer();
+ configurePathMatching(this.pathMatchConfigurer);
+ }
+ return this.pathMatchConfigurer;
+ }
+
+ /**
+ * Override to configure path matching options.
+ */
+ public void configurePathMatching(PathMatchConfigurer configurer) {
+ }
+
+ @Bean
+ public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
+ RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
+
+ List resolvers = new ArrayList<>();
+ addArgumentResolvers(resolvers);
+ if (!resolvers.isEmpty()) {
+ adapter.setCustomArgumentResolvers(resolvers);
+ }
+
+ adapter.setMessageConverters(getMessageConverters());
+ adapter.setConversionService(mvcConversionService());
+
+ return adapter;
+ }
+
+ /**
+ * Override to plug a sub-class of {@link RequestMappingHandlerAdapter}.
+ */
+ protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
+ return new RequestMappingHandlerAdapter();
+ }
+
+ /**
+ * Provide custom argument resolvers without overriding the built-in ones.
+ */
+ protected void addArgumentResolvers(List resolvers) {
+ }
+
+ /**
+ * Main method to access message converters to use for decoding
+ * controller method arguments and encoding return values.
+ * Use {@link #configureMessageConverters} to configure the list or
+ * {@link #extendMessageConverters} to add in addition to the default ones.
+ */
+ protected final List> getMessageConverters() {
+ if (this.messageConverters == null) {
+ this.messageConverters = new ArrayList<>();
+ configureMessageConverters(this.messageConverters);
+ if (this.messageConverters.isEmpty()) {
+ addDefaultHttpMessageConverters(this.messageConverters);
+ }
+ extendMessageConverters(this.messageConverters);
+ }
+ return this.messageConverters;
+ }
+
+ /**
+ * Override to configure the message converters to use for decoding
+ * controller method arguments and encoding return values.
+ * If no converters are specified, default will be added via
+ * {@link #addDefaultHttpMessageConverters}.
+ * @param converters a list to add converters to, initially an empty
+ */
+ protected void configureMessageConverters(List> converters) {
+ }
+
+ /**
+ * Adds default converters that sub-classes can call from
+ * {@link #configureMessageConverters(List)}.
+ */
+ protected final void addDefaultHttpMessageConverters(List> converters) {
+ converters.add(converter(new ByteBufferEncoder(), new ByteBufferDecoder()));
+ converters.add(converter(new StringEncoder(), new StringDecoder()));
+ converters.add(new ResourceHttpMessageConverter());
+ if (jaxb2Present) {
+ converters.add(converter(new Jaxb2Encoder(), new Jaxb2Decoder()));
+ }
+ if (jackson2Present) {
+ JsonObjectDecoder objectDecoder = new JsonObjectDecoder();
+ converters.add(converter(new JacksonJsonEncoder(), new JacksonJsonDecoder(objectDecoder)));
+ }
+ }
+
+ private static HttpMessageConverter converter(Encoder encoder, Decoder decoder) {
+ return new CodecHttpMessageConverter<>(encoder, decoder);
+ }
+
+ /**
+ * Override this to modify the list of converters after it has been
+ * configured, for example to add some in addition to the default ones.
+ */
+ protected void extendMessageConverters(List> converters) {
+ }
+
+ // TODO: switch to DefaultFormattingConversionService
+
+ @Bean
+ public GenericConversionService mvcConversionService() {
+ GenericConversionService service = new GenericConversionService();
+ addFormatters(service);
+ return service;
+ }
+
+ // TODO: switch to FormatterRegistry
+
+ /**
+ * Override to add custom {@link Converter}s and {@link Formatter}s.
+ * By default this method method registers:
+ *
+ * - {@link ReactiveStreamsToCompletableFutureConverter}
+ *
- {@link ReactiveStreamsToRxJava1Converter}
+ *
+ */
+ protected void addFormatters(ConverterRegistry registry) {
+ registry.addConverter(new ReactiveStreamsToCompletableFutureConverter());
+ if (DependencyUtils.hasRxJava1()) {
+ registry.addConverter(new ReactiveStreamsToRxJava1Converter());
+ }
+ }
+
+ @Bean
+ public SimpleHandlerAdapter simpleHandlerAdapter() {
+ return new SimpleHandlerAdapter();
+ }
+
+ @Bean
+ public ResponseBodyResultHandler responseBodyResultHandler() {
+ return new ResponseBodyResultHandler(getMessageConverters(), mvcConversionService());
+ }
+
+ @Bean
+ public SimpleResultHandler simpleResultHandler() {
+ return new SimpleResultHandler(mvcConversionService());
+ }
+
+ @Bean
+ public ViewResolutionResultHandler viewResolutionResultHandler() {
+ ViewResolverRegistry registry = new ViewResolverRegistry(this.applicationContext);
+ configureViewResolvers(registry);
+ List resolvers = registry.getViewResolvers();
+ ViewResolutionResultHandler handler = new ViewResolutionResultHandler(resolvers, mvcConversionService());
+ handler.setDefaultViews(registry.getDefaultViews());
+ handler.setOrder(registry.getOrder());
+ return handler;
+
+ }
+
+ /**
+ * Override this to configure view resolution.
+ */
+ protected void configureViewResolvers(ViewResolverRegistry registry) {
+ }
+
+}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/package-info.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/package-info.java
new file mode 100644
index 00000000000..8ada9a8cd4c
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Defines Spring Web Reactive configuration.
+ */
+package org.springframework.web.reactive.config;
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/config/ViewResolverRegistryTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/config/ViewResolverRegistryTests.java
new file mode 100644
index 00000000000..6e46b829b5c
--- /dev/null
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/config/ViewResolverRegistryTests.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.config;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.core.Ordered;
+import org.springframework.core.codec.support.JacksonJsonEncoder;
+import org.springframework.web.context.support.StaticWebApplicationContext;
+import org.springframework.web.reactive.result.view.HttpMessageConverterView;
+import org.springframework.web.reactive.result.view.UrlBasedViewResolver;
+import org.springframework.web.reactive.result.view.View;
+import org.springframework.web.reactive.result.view.freemarker.FreeMarkerConfigurer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for {@link ViewResolverRegistry}.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class ViewResolverRegistryTests {
+
+ private ViewResolverRegistry registry;
+
+
+ @Before
+ public void setUp() {
+ StaticWebApplicationContext context = new StaticWebApplicationContext();
+ context.registerSingleton("freeMarkerConfigurer", FreeMarkerConfigurer.class);
+ this.registry = new ViewResolverRegistry(context);
+ }
+
+ @Test
+ public void order() {
+ assertEquals(Ordered.LOWEST_PRECEDENCE, this.registry.getOrder());
+ }
+
+ @Test
+ public void hasRegistrations() {
+ assertFalse(this.registry.hasRegistrations());
+
+ this.registry.freeMarker();
+ assertTrue(this.registry.hasRegistrations());
+ }
+
+ @Test
+ public void noResolvers() {
+ assertNotNull(this.registry.getViewResolvers());
+ assertEquals(0, this.registry.getViewResolvers().size());
+ assertFalse(this.registry.hasRegistrations());
+ }
+
+ @Test
+ public void customViewResolver() {
+ UrlBasedViewResolver viewResolver = new UrlBasedViewResolver();
+ this.registry.viewResolver(viewResolver);
+
+ assertSame(viewResolver, this.registry.getViewResolvers().get(0));
+ assertEquals(1, this.registry.getViewResolvers().size());
+ }
+
+ @Test
+ public void defaultViews() throws Exception {
+ View view = new HttpMessageConverterView(new JacksonJsonEncoder());
+ this.registry.defaultViews(view);
+
+ assertEquals(1, this.registry.getDefaultViews().size());
+ assertSame(view, this.registry.getDefaultViews().get(0));
+ }
+
+}
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/config/WebReactiveConfigurationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/config/WebReactiveConfigurationTests.java
new file mode 100644
index 00000000000..74ddf2f7933
--- /dev/null
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/config/WebReactiveConfigurationTests.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.config;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import rx.Observable;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.core.ResolvableType;
+import org.springframework.core.codec.support.JacksonJsonEncoder;
+import org.springframework.core.codec.support.Jaxb2Decoder;
+import org.springframework.core.codec.support.Jaxb2Encoder;
+import org.springframework.core.codec.support.Pojo;
+import org.springframework.core.codec.support.StringDecoder;
+import org.springframework.core.codec.support.StringEncoder;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.io.Resource;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.reactive.CodecHttpMessageConverter;
+import org.springframework.http.converter.reactive.HttpMessageConverter;
+import org.springframework.http.server.reactive.MockServerHttpRequest;
+import org.springframework.http.server.reactive.MockServerHttpResponse;
+import org.springframework.util.MimeType;
+import org.springframework.util.MimeTypeUtils;
+import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
+import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
+import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
+import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler;
+import org.springframework.web.reactive.result.view.HttpMessageConverterView;
+import org.springframework.web.reactive.result.view.View;
+import org.springframework.web.reactive.result.view.ViewResolutionResultHandler;
+import org.springframework.web.reactive.result.view.ViewResolver;
+import org.springframework.web.reactive.result.view.freemarker.FreeMarkerConfigurer;
+import org.springframework.web.reactive.result.view.freemarker.FreeMarkerViewResolver;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.adapter.DefaultServerWebExchange;
+import org.springframework.web.server.session.WebSessionManager;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Unit tests for {@link WebReactiveConfiguration}.
+ * @author Rossen Stoyanchev
+ */
+public class WebReactiveConfigurationTests {
+
+ private MockServerHttpRequest request;
+
+ private ServerWebExchange exchange;
+
+
+ @Before
+ public void setUp() throws Exception {
+ this.request = new MockServerHttpRequest(HttpMethod.GET, new URI("/"));
+ MockServerHttpResponse response = new MockServerHttpResponse();
+ this.exchange = new DefaultServerWebExchange(this.request, response, mock(WebSessionManager.class));
+ }
+
+
+ @Test
+ public void requestMappingHandlerMapping() throws Exception {
+ ApplicationContext context = loadConfig(WebReactiveConfiguration.class);
+
+ String name = "requestMappingHandlerMapping";
+ RequestMappingHandlerMapping mapping = context.getBean(name, RequestMappingHandlerMapping.class);
+ assertNotNull(mapping);
+
+ assertEquals(0, mapping.getOrder());
+
+ assertTrue(mapping.useSuffixPatternMatch());
+ assertTrue(mapping.useTrailingSlashMatch());
+ assertTrue(mapping.useRegisteredSuffixPatternMatch());
+
+ name = "mvcContentTypeResolver";
+ RequestedContentTypeResolver resolver = context.getBean(name, RequestedContentTypeResolver.class);
+ assertSame(resolver, mapping.getContentTypeResolver());
+
+ this.request.setUri(new URI("/path.json"));
+ List list = Collections.singletonList(MediaType.APPLICATION_JSON);
+ assertEquals(list, resolver.resolveMediaTypes(this.exchange));
+
+ this.request.setUri(new URI("/path.xml"));
+ assertEquals(Collections.emptyList(), resolver.resolveMediaTypes(this.exchange));
+ }
+
+ @Test
+ public void customPathMatchConfig() throws Exception {
+ ApplicationContext context = loadConfig(CustomPatchMatchConfig.class);
+
+ String name = "requestMappingHandlerMapping";
+ RequestMappingHandlerMapping mapping = context.getBean(name, RequestMappingHandlerMapping.class);
+ assertNotNull(mapping);
+
+ assertFalse(mapping.useSuffixPatternMatch());
+ assertFalse(mapping.useTrailingSlashMatch());
+ }
+
+ @Test
+ public void requestMappingHandlerAdapter() throws Exception {
+ ApplicationContext context = loadConfig(WebReactiveConfiguration.class);
+
+ String name = "requestMappingHandlerAdapter";
+ RequestMappingHandlerAdapter adapter = context.getBean(name, RequestMappingHandlerAdapter.class);
+ assertNotNull(adapter);
+
+ List> converters = adapter.getMessageConverters();
+ assertEquals(5, converters.size());
+
+ assertHasConverter(converters, ByteBuffer.class, MediaType.APPLICATION_OCTET_STREAM);
+ assertHasConverter(converters, String.class, MediaType.TEXT_PLAIN);
+ assertHasConverter(converters, Resource.class, MediaType.IMAGE_PNG);
+ assertHasConverter(converters, Pojo.class, MediaType.APPLICATION_XML);
+ assertHasConverter(converters, Pojo.class, MediaType.APPLICATION_JSON);
+
+ name = "mvcConversionService";
+ ConversionService service = context.getBean(name, ConversionService.class);
+ assertSame(service, adapter.getConversionService());
+ }
+
+ @Test
+ public void customMessageConverterConfig() throws Exception {
+ ApplicationContext context = loadConfig(CustomMessageConverterConfig.class);
+
+ String name = "requestMappingHandlerAdapter";
+ RequestMappingHandlerAdapter adapter = context.getBean(name, RequestMappingHandlerAdapter.class);
+ assertNotNull(adapter);
+
+ List> converters = adapter.getMessageConverters();
+ assertEquals(2, converters.size());
+
+ assertHasConverter(converters, String.class, MediaType.TEXT_PLAIN);
+ assertHasConverter(converters, Pojo.class, MediaType.APPLICATION_XML);
+ }
+
+ @Test
+ public void mvcConversionService() throws Exception {
+ ApplicationContext context = loadConfig(WebReactiveConfiguration.class);
+
+ String name = "mvcConversionService";
+ ConversionService service = context.getBean(name, ConversionService.class);
+ assertNotNull(service);
+
+ service.canConvert(CompletableFuture.class, Mono.class);
+ service.canConvert(Observable.class, Flux.class);
+ }
+
+
+ @Test
+ public void responseBodyResultHandler() throws Exception {
+ ApplicationContext context = loadConfig(WebReactiveConfiguration.class);
+
+ String name = "responseBodyResultHandler";
+ ResponseBodyResultHandler handler = context.getBean(name, ResponseBodyResultHandler.class);
+ assertNotNull(handler);
+
+ assertEquals(0, handler.getOrder());
+
+ List> converters = handler.getMessageConverters();
+ assertEquals(5, converters.size());
+
+ assertHasConverter(converters, ByteBuffer.class, MediaType.APPLICATION_OCTET_STREAM);
+ assertHasConverter(converters, String.class, MediaType.TEXT_PLAIN);
+ assertHasConverter(converters, Resource.class, MediaType.IMAGE_PNG);
+ assertHasConverter(converters, Pojo.class, MediaType.APPLICATION_XML);
+ assertHasConverter(converters, Pojo.class, MediaType.APPLICATION_JSON);
+ }
+
+ @Test
+ public void viewResolutionResultHandler() throws Exception {
+ ApplicationContext context = loadConfig(CustomViewResolverConfig.class);
+
+ String name = "viewResolutionResultHandler";
+ ViewResolutionResultHandler handler = context.getBean(name, ViewResolutionResultHandler.class);
+ assertNotNull(handler);
+
+ assertEquals(Ordered.LOWEST_PRECEDENCE, handler.getOrder());
+
+ List resolvers = handler.getViewResolvers();
+ assertEquals(1, resolvers.size());
+ assertEquals(FreeMarkerViewResolver.class, resolvers.get(0).getClass());
+
+ List views = handler.getDefaultViews();
+ assertEquals(1, views.size());
+
+ MimeType type = MimeTypeUtils.parseMimeType("application/json;charset=UTF-8");
+ assertEquals(type, views.get(0).getSupportedMediaTypes().get(0));
+ }
+
+
+ private void assertHasConverter(List> converters, Class> clazz, MediaType mediaType) {
+ ResolvableType type = ResolvableType.forClass(clazz);
+ assertTrue(converters.stream()
+ .filter(c -> c.canRead(type, mediaType) && c.canWrite(type, mediaType))
+ .findAny()
+ .isPresent());
+ }
+
+ private ApplicationContext loadConfig(Class>... configurationClasses) {
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.register(configurationClasses);
+ context.refresh();
+ return context;
+ }
+
+
+ @Configuration
+ static class CustomPatchMatchConfig extends WebReactiveConfiguration {
+
+ @Override
+ public void configurePathMatching(PathMatchConfigurer configurer) {
+ configurer.setUseSuffixPatternMatch(false);
+ configurer.setUseTrailingSlashMatch(false);
+ }
+ }
+
+ @Configuration
+ static class CustomMessageConverterConfig extends WebReactiveConfiguration {
+
+ @Override
+ protected void configureMessageConverters(List> converters) {
+ converters.add(new CodecHttpMessageConverter<>(new StringEncoder(), new StringDecoder()));
+ }
+
+ @Override
+ protected void extendMessageConverters(List> converters) {
+ converters.add(new CodecHttpMessageConverter<>(new Jaxb2Encoder(), new Jaxb2Decoder()));
+ }
+ }
+
+ @Configuration @SuppressWarnings("unused")
+ static class CustomViewResolverConfig extends WebReactiveConfiguration {
+
+ @Override
+ protected void configureViewResolvers(ViewResolverRegistry registry) {
+ registry.freeMarker();
+ registry.defaultViews(new HttpMessageConverterView(new JacksonJsonEncoder()));
+ }
+
+ @Bean
+ public FreeMarkerConfigurer freeMarkerConfig() {
+ return new FreeMarkerConfigurer();
+ }
+
+ }
+}
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java
index 217c52a6c26..3ad58a92b0a 100644
--- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java
@@ -21,7 +21,6 @@ import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import javax.xml.bind.annotation.XmlElement;
@@ -40,19 +39,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType;
-import org.springframework.core.codec.support.ByteBufferDecoder;
-import org.springframework.core.codec.support.ByteBufferEncoder;
-import org.springframework.core.codec.support.JacksonJsonDecoder;
import org.springframework.core.codec.support.JacksonJsonEncoder;
-import org.springframework.core.codec.support.Jaxb2Decoder;
-import org.springframework.core.codec.support.Jaxb2Encoder;
-import org.springframework.core.codec.support.JsonObjectDecoder;
-import org.springframework.core.codec.support.StringDecoder;
-import org.springframework.core.codec.support.StringEncoder;
-import org.springframework.core.convert.ConversionService;
-import org.springframework.core.convert.support.GenericConversionService;
-import org.springframework.core.convert.support.ReactiveStreamsToCompletableFutureConverter;
-import org.springframework.core.convert.support.ReactiveStreamsToRxJava1Converter;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.buffer.DataBuffer;
@@ -62,9 +49,6 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
-import org.springframework.http.converter.reactive.CodecHttpMessageConverter;
-import org.springframework.http.converter.reactive.HttpMessageConverter;
-import org.springframework.http.converter.reactive.ResourceHttpMessageConverter;
import org.springframework.http.server.reactive.AbstractHttpHandlerIntegrationTests;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ZeroCopyIntegrationTests;
@@ -78,11 +62,9 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.DispatcherHandler;
-import org.springframework.web.reactive.result.SimpleResultHandler;
-import org.springframework.web.reactive.result.view.ViewResolutionResultHandler;
-import org.springframework.web.reactive.result.view.ViewResolver;
+import org.springframework.web.reactive.config.ViewResolverRegistry;
+import org.springframework.web.reactive.config.WebReactiveConfiguration;
import org.springframework.web.reactive.result.view.freemarker.FreeMarkerConfigurer;
-import org.springframework.web.reactive.result.view.freemarker.FreeMarkerViewResolver;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import static org.junit.Assert.assertArrayEquals;
@@ -385,61 +367,11 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati
@Configuration
@SuppressWarnings("unused")
- static class FrameworkConfig {
+ static class FrameworkConfig extends WebReactiveConfiguration {
- @Bean
- public RequestMappingHandlerMapping handlerMapping() {
- return new RequestMappingHandlerMapping();
- }
-
- @Bean
- public RequestMappingHandlerAdapter handlerAdapter() {
- RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
- handlerAdapter.setMessageConverters(getDefaultMessageConverters());
- handlerAdapter.setConversionService(conversionService());
- return handlerAdapter;
- }
-
- private List> getDefaultMessageConverters() {
- return Arrays.asList(
- new CodecHttpMessageConverter<>(new ByteBufferEncoder(), new ByteBufferDecoder()),
- new CodecHttpMessageConverter<>(new StringEncoder(), new StringDecoder()),
- new CodecHttpMessageConverter<>(new Jaxb2Encoder(), new Jaxb2Decoder()),
- new CodecHttpMessageConverter<>(new JacksonJsonEncoder(),
- new JacksonJsonDecoder(new JsonObjectDecoder())));
- }
-
- @Bean
- public ConversionService conversionService() {
- // TODO: test failures with DefaultConversionService
- GenericConversionService service = new GenericConversionService();
- service.addConverter(new ReactiveStreamsToCompletableFutureConverter());
- service.addConverter(new ReactiveStreamsToRxJava1Converter());
- return service;
- }
-
- @Bean
- public ResponseBodyResultHandler responseBodyResultHandler() {
- List> converters = new ArrayList<>();
- converters.add(new ResourceHttpMessageConverter());
- converters.addAll(getDefaultMessageConverters());
- return new ResponseBodyResultHandler(converters, conversionService());
- }
-
- @Bean
- public SimpleResultHandler simpleHandlerResultHandler() {
- return new SimpleResultHandler(conversionService());
- }
-
- @Bean
- public ViewResolutionResultHandler viewResolverResultHandler() {
- List resolvers = Collections.singletonList(freeMarkerViewResolver());
- return new ViewResolutionResultHandler(resolvers, conversionService());
- }
-
- @Bean
- public ViewResolver freeMarkerViewResolver() {
- return new FreeMarkerViewResolver("", ".ftl");
+ @Override
+ protected void configureViewResolvers(ViewResolverRegistry registry) {
+ registry.freeMarker();
}
@Bean