diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
index 190384c4420f0f55540424c83ecc2e723e55820f..ec220dbf2efc6020ba43905a316de6a681dd0744 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 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.
@@ -77,6 +77,8 @@ import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ViewResolver;
+import org.springframework.web.servlet.function.support.HandlerFunctionAdapter;
+import org.springframework.web.servlet.function.support.RouterFunctionMapping;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor;
@@ -474,6 +476,27 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
return mapping;
}
+ /**
+ * Return a {@link RouterFunctionMapping} ordered at 3 to map
+ * {@linkplain org.springframework.web.servlet.function.RouterFunction router functions}.
+ * Consider overriding one of these other more fine-grained methods:
+ *
+ * - {@link #addInterceptors} for adding handler interceptors.
+ *
- {@link #addCorsMappings} to configure cross origin requests processing.
+ *
- {@link #configureMessageConverters} for adding custom message converters.
+ *
+ * @since 5.2
+ */
+ @Bean
+ public RouterFunctionMapping routerFunctionMapping() {
+ RouterFunctionMapping mapping = new RouterFunctionMapping();
+ mapping.setOrder(3);
+ mapping.setInterceptors(getInterceptors());
+ mapping.setCorsConfigurations(getCorsConfigurations());
+ mapping.setMessageConverters(getMessageConverters());
+ return mapping;
+ }
+
/**
* Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped
* resource handlers. To configure resource handling, override
@@ -593,6 +616,16 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
return new RequestMappingHandlerAdapter();
}
+ /**
+ * Returns a {@link HandlerFunctionAdapter} for processing requests through
+ * {@linkplain org.springframework.web.servlet.function.HandlerFunction handler functions}.
+ * @since 5.2
+ */
+ @Bean
+ public HandlerFunctionAdapter handlerFunctionAdapter() {
+ return new HandlerFunctionAdapter();
+ }
+
/**
* Return the {@link ConfigurableWebBindingInitializer} to use for
* initializing all {@link WebDataBinder} instances.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/HandlerFunctionAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/HandlerFunctionAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..ad3358e16520773783d6915ebc38405a713cedbd
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/HandlerFunctionAdapter.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2002-2019 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.servlet.function.support;
+
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.core.Ordered;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.web.servlet.HandlerAdapter;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.function.HandlerFunction;
+import org.springframework.web.servlet.function.RouterFunctions;
+import org.springframework.web.servlet.function.ServerRequest;
+import org.springframework.web.servlet.function.ServerResponse;
+
+/**
+ * {@code HandlerAdapter} implementation that supports {@link HandlerFunction}s.
+ *
+ * @author Arjen Poutsma
+ * @since 5.2
+ */
+public class HandlerFunctionAdapter implements HandlerAdapter, Ordered {
+
+ private int order = Ordered.LOWEST_PRECEDENCE;
+
+
+ /**
+ * Specify the order value for this HandlerAdapter bean.
+ * The default value is {@code Ordered.LOWEST_PRECEDENCE}, meaning non-ordered.
+ * @see org.springframework.core.Ordered#getOrder()
+ */
+ public void setOrder(int order) {
+ this.order = order;
+ }
+
+ @Override
+ public int getOrder() {
+ return this.order;
+ }
+
+ @Override
+ public boolean supports(Object handler) {
+ return handler instanceof HandlerFunction;
+ }
+
+ @Nullable
+ @Override
+ public ModelAndView handle(HttpServletRequest servletRequest,
+ HttpServletResponse servletResponse,
+ Object handler) throws Exception {
+
+
+ HandlerFunction> handlerFunction = (HandlerFunction>) handler;
+
+ ServerRequest serverRequest = getServerRequest(servletRequest);
+ ServerResponse serverResponse = handlerFunction.handle(serverRequest);
+
+ return serverResponse.writeTo(servletRequest, servletResponse,
+ new ServerRequestContext(serverRequest));
+ }
+
+ private ServerRequest getServerRequest(HttpServletRequest servletRequest) {
+ ServerRequest serverRequest =
+ (ServerRequest) servletRequest.getAttribute(RouterFunctions.REQUEST_ATTRIBUTE);
+ Assert.state(serverRequest != null, () -> "Required attribute '" +
+ RouterFunctions.REQUEST_ATTRIBUTE + "' is missing");
+ return serverRequest;
+ }
+
+ @Override
+ public long getLastModified(HttpServletRequest request, Object handler) {
+ return -1L;
+ }
+
+
+ private static class ServerRequestContext implements ServerResponse.Context {
+
+ private final ServerRequest serverRequest;
+
+
+ public ServerRequestContext(ServerRequest serverRequest) {
+ this.serverRequest = serverRequest;
+ }
+
+ @Override
+ public List> messageConverters() {
+ return this.serverRequest.messageConverters();
+ }
+ }
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/RouterFunctionMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/RouterFunctionMapping.java
new file mode 100644
index 0000000000000000000000000000000000000000..791368dd9cbd22137af1669a04fb00698e53c6b9
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/RouterFunctionMapping.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2002-2019 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.servlet.function.support;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+
+import org.jetbrains.annotations.NotNull;
+
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.http.converter.ByteArrayHttpMessageConverter;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
+import org.springframework.http.converter.xml.SourceHttpMessageConverter;
+import org.springframework.lang.Nullable;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.servlet.function.RouterFunction;
+import org.springframework.web.servlet.function.RouterFunctions;
+import org.springframework.web.servlet.function.ServerRequest;
+import org.springframework.web.servlet.handler.AbstractHandlerMapping;
+
+/**
+ * {@code HandlerMapping} implementation that supports {@link RouterFunction}s.
+ * If no {@link RouterFunction} is provided at
+ * {@linkplain #RouterFunctionMapping(RouterFunction) construction time}, this mapping will detect
+ * all router functions in the application context, and consult them in
+ * {@linkplain org.springframework.core.annotation.Order order}.
+ *
+ * @author Arjen Poutsma
+ * @since 5.2
+ */
+public class RouterFunctionMapping extends AbstractHandlerMapping implements InitializingBean {
+
+ @Nullable
+ private RouterFunction> routerFunction;
+
+ private List> messageConverters = Collections.emptyList();
+
+ private boolean detectHandlerFunctionsInAncestorContexts = false;
+
+
+
+ /**
+ * Create an empty {@code RouterFunctionMapping}.
+ * If this constructor is used, this mapping will detect all {@link RouterFunction} instances
+ * available in the application context.
+ */
+ public RouterFunctionMapping() {
+ }
+
+ /**
+ * Create a {@code RouterFunctionMapping} with the given {@link RouterFunction}.
+ *
If this constructor is used, no application context detection will occur.
+ * @param routerFunction the router function to use for mapping
+ */
+ public RouterFunctionMapping(RouterFunction> routerFunction) {
+ this.routerFunction = routerFunction;
+ }
+
+ /**
+ * Set the router function to map to.
+ *
If this property is used, no application context detection will occur.
+ */
+ public void setRouterFunction(@Nullable RouterFunction> routerFunction) {
+ this.routerFunction = routerFunction;
+ }
+
+ /**
+ * Return the configured {@link RouterFunction}.
+ *
Note: When router functions are detected from the
+ * ApplicationContext, this method may return {@code null} if invoked
+ * prior to {@link #afterPropertiesSet()}.
+ * @return the router function or {@code null}
+ */
+ @Nullable
+ public RouterFunction> getRouterFunction() {
+ return this.routerFunction;
+ }
+
+ public void setMessageConverters(List> messageConverters) {
+ this.messageConverters = messageConverters;
+ }
+
+ /**
+ * Set whether to detect handler functions in ancestor ApplicationContexts.
+ * Default is "false": Only handler functions in the current ApplicationContext
+ * will be detected, i.e. only in the context that this HandlerMapping itself
+ * is defined in (typically the current DispatcherServlet's context).
+ *
Switch this flag on to detect handler beans in ancestor contexts
+ * (typically the Spring root WebApplicationContext) as well.
+ */
+ public void setDetectHandlerFunctionsInAncestorContexts(boolean detectHandlerFunctionsInAncestorContexts) {
+ this.detectHandlerFunctionsInAncestorContexts = detectHandlerFunctionsInAncestorContexts;
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ if (this.routerFunction == null) {
+ initRouterFunction();
+ }
+ if (CollectionUtils.isEmpty(this.messageConverters)) {
+ initMessageConverters();
+ }
+ }
+
+ /**
+ * Detect a all {@linkplain RouterFunction router functions} in the
+ * current application context.
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private void initRouterFunction() {
+ ApplicationContext applicationContext = obtainApplicationContext();
+ Map beans =
+ (this.detectHandlerFunctionsInAncestorContexts ?
+ BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RouterFunction.class) :
+ applicationContext.getBeansOfType(RouterFunction.class));
+
+ List routerFunctions = new ArrayList<>(beans.values());
+ if (!CollectionUtils.isEmpty(routerFunctions) && logger.isInfoEnabled()) {
+ routerFunctions.forEach(routerFunction -> logger.info("Mapped " + routerFunction));
+ }
+ this.routerFunction = routerFunctions.stream()
+ .reduce(RouterFunction::andOther)
+ .orElse(null);
+ }
+
+ /**
+ * Initializes a default set of {@linkplain HttpMessageConverter message converters}.
+ */
+ private void initMessageConverters() {
+ List> messageConverters = new ArrayList<>(4);
+
+ messageConverters.add(new ByteArrayHttpMessageConverter());
+
+ StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
+ stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
+ messageConverters.add(stringHttpMessageConverter);
+
+ try {
+ messageConverters.add(new SourceHttpMessageConverter<>());
+ }
+ catch (Error err) {
+ // Ignore when no TransformerFactory implementation is available
+ }
+ messageConverters.add(new AllEncompassingFormHttpMessageConverter());
+
+ this.messageConverters = messageConverters;
+ }
+
+ @Nullable
+ @Override
+ protected Object getHandlerInternal(@NotNull HttpServletRequest servletRequest) throws Exception {
+ if (this.routerFunction != null) {
+ ServerRequest request = ServerRequest.create(servletRequest, this.messageConverters);
+ servletRequest.setAttribute(RouterFunctions.REQUEST_ATTRIBUTE, request);
+ return this.routerFunction.route(request).orElse(null);
+ }
+ else {
+ return null;
+ }
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/package-info.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/package-info.java
new file mode 100644
index 0000000000000000000000000000000000000000..b5c58ed0ef65092e0daef81978736bca3b8e0b64
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2002-2019 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.
+ */
+
+/**
+ * Classes supporting the {@code org.springframework.web.servlet.function} package.
+ * Contains a {@code HandlerAdapter} that supports {@code HandlerFunction}s,
+ * and a {@code HandlerMapping} that supports {@code RouterFunction}s.
+ */
+@NonNullApi
+@NonNullFields
+package org.springframework.web.servlet.function.support;
+
+import org.springframework.lang.NonNullApi;
+import org.springframework.lang.NonNullFields;
diff --git a/spring-webmvc/src/main/resources/org/springframework/web/servlet/DispatcherServlet.properties b/spring-webmvc/src/main/resources/org/springframework/web/servlet/DispatcherServlet.properties
index 52920de27ddb0c5a2e693a3fbd5d72d274ea0c64..c55b61864e96252f562820c726f3da9c0f026c18 100644
--- a/spring-webmvc/src/main/resources/org/springframework/web/servlet/DispatcherServlet.properties
+++ b/spring-webmvc/src/main/resources/org/springframework/web/servlet/DispatcherServlet.properties
@@ -7,11 +7,14 @@ org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
- org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
+ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
+ org.springframework.web.servlet.function.support.RouterFunctionMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
- org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
+ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
+ org.springframework.web.servlet.function.support.HandlerFunctionAdapter
+
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\