Add Servlet.fn integration with DispatcherServlet
This commit adds support for HandlerFunctions and RouterFunctions to DispatcherServlet, in the form of a HandlerAdapter and HandlerMapping. See gh-21490
This commit is contained in:
parent
85082fc10c
commit
e638fef508
|
@ -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:
|
||||
* <ul>
|
||||
* <li>{@link #addInterceptors} for adding handler interceptors.
|
||||
* <li>{@link #addCorsMappings} to configure cross origin requests processing.
|
||||
* <li>{@link #configureMessageConverters} for adding custom message converters.
|
||||
* </ul>
|
||||
* @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.
|
||||
|
|
|
@ -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.
|
||||
* <p>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<HttpMessageConverter<?>> messageConverters() {
|
||||
return this.serverRequest.messageConverters();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
* <p>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<HttpMessageConverter<?>> messageConverters = Collections.emptyList();
|
||||
|
||||
private boolean detectHandlerFunctionsInAncestorContexts = false;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create an empty {@code RouterFunctionMapping}.
|
||||
* <p>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}.
|
||||
* <p>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.
|
||||
* <p>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}.
|
||||
* <p><strong>Note:</strong> 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<HttpMessageConverter<?>> messageConverters) {
|
||||
this.messageConverters = messageConverters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to detect handler functions in ancestor ApplicationContexts.
|
||||
* <p>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).
|
||||
* <p>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<String, RouterFunction> beans =
|
||||
(this.detectHandlerFunctionsInAncestorContexts ?
|
||||
BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RouterFunction.class) :
|
||||
applicationContext.getBeansOfType(RouterFunction.class));
|
||||
|
||||
List<RouterFunction> 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<HttpMessageConverter<?>> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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,\
|
||||
|
|
Loading…
Reference in New Issue