Skip to content

Commit e638fef

Browse files
committed
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 spring-projectsgh-21490
1 parent 85082fc commit e638fef

File tree

5 files changed

+357
-3
lines changed

5 files changed

+357
-3
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -77,6 +77,8 @@
7777
import org.springframework.web.servlet.HandlerExceptionResolver;
7878
import org.springframework.web.servlet.HandlerMapping;
7979
import org.springframework.web.servlet.ViewResolver;
80+
import org.springframework.web.servlet.function.support.HandlerFunctionAdapter;
81+
import org.springframework.web.servlet.function.support.RouterFunctionMapping;
8082
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
8183
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
8284
import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor;
@@ -474,6 +476,27 @@ public BeanNameUrlHandlerMapping beanNameHandlerMapping() {
474476
return mapping;
475477
}
476478

479+
/**
480+
* Return a {@link RouterFunctionMapping} ordered at 3 to map
481+
* {@linkplain org.springframework.web.servlet.function.RouterFunction router functions}.
482+
* Consider overriding one of these other more fine-grained methods:
483+
* <ul>
484+
* <li>{@link #addInterceptors} for adding handler interceptors.
485+
* <li>{@link #addCorsMappings} to configure cross origin requests processing.
486+
* <li>{@link #configureMessageConverters} for adding custom message converters.
487+
* </ul>
488+
* @since 5.2
489+
*/
490+
@Bean
491+
public RouterFunctionMapping routerFunctionMapping() {
492+
RouterFunctionMapping mapping = new RouterFunctionMapping();
493+
mapping.setOrder(3);
494+
mapping.setInterceptors(getInterceptors());
495+
mapping.setCorsConfigurations(getCorsConfigurations());
496+
mapping.setMessageConverters(getMessageConverters());
497+
return mapping;
498+
}
499+
477500
/**
478501
* Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped
479502
* resource handlers. To configure resource handling, override
@@ -593,6 +616,16 @@ protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
593616
return new RequestMappingHandlerAdapter();
594617
}
595618

619+
/**
620+
* Returns a {@link HandlerFunctionAdapter} for processing requests through
621+
* {@linkplain org.springframework.web.servlet.function.HandlerFunction handler functions}.
622+
* @since 5.2
623+
*/
624+
@Bean
625+
public HandlerFunctionAdapter handlerFunctionAdapter() {
626+
return new HandlerFunctionAdapter();
627+
}
628+
596629
/**
597630
* Return the {@link ConfigurableWebBindingInitializer} to use for
598631
* initializing all {@link WebDataBinder} instances.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.servlet.function.support;
18+
19+
import java.util.List;
20+
import javax.servlet.http.HttpServletRequest;
21+
import javax.servlet.http.HttpServletResponse;
22+
23+
import org.springframework.core.Ordered;
24+
import org.springframework.http.converter.HttpMessageConverter;
25+
import org.springframework.lang.Nullable;
26+
import org.springframework.util.Assert;
27+
import org.springframework.web.servlet.HandlerAdapter;
28+
import org.springframework.web.servlet.ModelAndView;
29+
import org.springframework.web.servlet.function.HandlerFunction;
30+
import org.springframework.web.servlet.function.RouterFunctions;
31+
import org.springframework.web.servlet.function.ServerRequest;
32+
import org.springframework.web.servlet.function.ServerResponse;
33+
34+
/**
35+
* {@code HandlerAdapter} implementation that supports {@link HandlerFunction}s.
36+
*
37+
* @author Arjen Poutsma
38+
* @since 5.2
39+
*/
40+
public class HandlerFunctionAdapter implements HandlerAdapter, Ordered {
41+
42+
private int order = Ordered.LOWEST_PRECEDENCE;
43+
44+
45+
/**
46+
* Specify the order value for this HandlerAdapter bean.
47+
* <p>The default value is {@code Ordered.LOWEST_PRECEDENCE}, meaning non-ordered.
48+
* @see org.springframework.core.Ordered#getOrder()
49+
*/
50+
public void setOrder(int order) {
51+
this.order = order;
52+
}
53+
54+
@Override
55+
public int getOrder() {
56+
return this.order;
57+
}
58+
59+
@Override
60+
public boolean supports(Object handler) {
61+
return handler instanceof HandlerFunction;
62+
}
63+
64+
@Nullable
65+
@Override
66+
public ModelAndView handle(HttpServletRequest servletRequest,
67+
HttpServletResponse servletResponse,
68+
Object handler) throws Exception {
69+
70+
71+
HandlerFunction<?> handlerFunction = (HandlerFunction<?>) handler;
72+
73+
ServerRequest serverRequest = getServerRequest(servletRequest);
74+
ServerResponse serverResponse = handlerFunction.handle(serverRequest);
75+
76+
return serverResponse.writeTo(servletRequest, servletResponse,
77+
new ServerRequestContext(serverRequest));
78+
}
79+
80+
private ServerRequest getServerRequest(HttpServletRequest servletRequest) {
81+
ServerRequest serverRequest =
82+
(ServerRequest) servletRequest.getAttribute(RouterFunctions.REQUEST_ATTRIBUTE);
83+
Assert.state(serverRequest != null, () -> "Required attribute '" +
84+
RouterFunctions.REQUEST_ATTRIBUTE + "' is missing");
85+
return serverRequest;
86+
}
87+
88+
@Override
89+
public long getLastModified(HttpServletRequest request, Object handler) {
90+
return -1L;
91+
}
92+
93+
94+
private static class ServerRequestContext implements ServerResponse.Context {
95+
96+
private final ServerRequest serverRequest;
97+
98+
99+
public ServerRequestContext(ServerRequest serverRequest) {
100+
this.serverRequest = serverRequest;
101+
}
102+
103+
@Override
104+
public List<HttpMessageConverter<?>> messageConverters() {
105+
return this.serverRequest.messageConverters();
106+
}
107+
}
108+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.servlet.function.support;
18+
19+
import java.util.ArrayList;
20+
import java.util.Collections;
21+
import java.util.List;
22+
import java.util.Map;
23+
import javax.servlet.http.HttpServletRequest;
24+
25+
import org.jetbrains.annotations.NotNull;
26+
27+
import org.springframework.beans.factory.BeanFactoryUtils;
28+
import org.springframework.beans.factory.InitializingBean;
29+
import org.springframework.context.ApplicationContext;
30+
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
31+
import org.springframework.http.converter.HttpMessageConverter;
32+
import org.springframework.http.converter.StringHttpMessageConverter;
33+
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
34+
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
35+
import org.springframework.lang.Nullable;
36+
import org.springframework.util.CollectionUtils;
37+
import org.springframework.web.servlet.function.RouterFunction;
38+
import org.springframework.web.servlet.function.RouterFunctions;
39+
import org.springframework.web.servlet.function.ServerRequest;
40+
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
41+
42+
/**
43+
* {@code HandlerMapping} implementation that supports {@link RouterFunction}s.
44+
* <p>If no {@link RouterFunction} is provided at
45+
* {@linkplain #RouterFunctionMapping(RouterFunction) construction time}, this mapping will detect
46+
* all router functions in the application context, and consult them in
47+
* {@linkplain org.springframework.core.annotation.Order order}.
48+
*
49+
* @author Arjen Poutsma
50+
* @since 5.2
51+
*/
52+
public class RouterFunctionMapping extends AbstractHandlerMapping implements InitializingBean {
53+
54+
@Nullable
55+
private RouterFunction<?> routerFunction;
56+
57+
private List<HttpMessageConverter<?>> messageConverters = Collections.emptyList();
58+
59+
private boolean detectHandlerFunctionsInAncestorContexts = false;
60+
61+
62+
63+
/**
64+
* Create an empty {@code RouterFunctionMapping}.
65+
* <p>If this constructor is used, this mapping will detect all {@link RouterFunction} instances
66+
* available in the application context.
67+
*/
68+
public RouterFunctionMapping() {
69+
}
70+
71+
/**
72+
* Create a {@code RouterFunctionMapping} with the given {@link RouterFunction}.
73+
* <p>If this constructor is used, no application context detection will occur.
74+
* @param routerFunction the router function to use for mapping
75+
*/
76+
public RouterFunctionMapping(RouterFunction<?> routerFunction) {
77+
this.routerFunction = routerFunction;
78+
}
79+
80+
/**
81+
* Set the router function to map to.
82+
* <p>If this property is used, no application context detection will occur.
83+
*/
84+
public void setRouterFunction(@Nullable RouterFunction<?> routerFunction) {
85+
this.routerFunction = routerFunction;
86+
}
87+
88+
/**
89+
* Return the configured {@link RouterFunction}.
90+
* <p><strong>Note:</strong> When router functions are detected from the
91+
* ApplicationContext, this method may return {@code null} if invoked
92+
* prior to {@link #afterPropertiesSet()}.
93+
* @return the router function or {@code null}
94+
*/
95+
@Nullable
96+
public RouterFunction<?> getRouterFunction() {
97+
return this.routerFunction;
98+
}
99+
100+
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
101+
this.messageConverters = messageConverters;
102+
}
103+
104+
/**
105+
* Set whether to detect handler functions in ancestor ApplicationContexts.
106+
* <p>Default is "false": Only handler functions in the current ApplicationContext
107+
* will be detected, i.e. only in the context that this HandlerMapping itself
108+
* is defined in (typically the current DispatcherServlet's context).
109+
* <p>Switch this flag on to detect handler beans in ancestor contexts
110+
* (typically the Spring root WebApplicationContext) as well.
111+
*/
112+
public void setDetectHandlerFunctionsInAncestorContexts(boolean detectHandlerFunctionsInAncestorContexts) {
113+
this.detectHandlerFunctionsInAncestorContexts = detectHandlerFunctionsInAncestorContexts;
114+
}
115+
116+
@Override
117+
public void afterPropertiesSet() throws Exception {
118+
if (this.routerFunction == null) {
119+
initRouterFunction();
120+
}
121+
if (CollectionUtils.isEmpty(this.messageConverters)) {
122+
initMessageConverters();
123+
}
124+
}
125+
126+
/**
127+
* Detect a all {@linkplain RouterFunction router functions} in the
128+
* current application context.
129+
*/
130+
@SuppressWarnings({"unchecked", "rawtypes"})
131+
private void initRouterFunction() {
132+
ApplicationContext applicationContext = obtainApplicationContext();
133+
Map<String, RouterFunction> beans =
134+
(this.detectHandlerFunctionsInAncestorContexts ?
135+
BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RouterFunction.class) :
136+
applicationContext.getBeansOfType(RouterFunction.class));
137+
138+
List<RouterFunction> routerFunctions = new ArrayList<>(beans.values());
139+
if (!CollectionUtils.isEmpty(routerFunctions) && logger.isInfoEnabled()) {
140+
routerFunctions.forEach(routerFunction -> logger.info("Mapped " + routerFunction));
141+
}
142+
this.routerFunction = routerFunctions.stream()
143+
.reduce(RouterFunction::andOther)
144+
.orElse(null);
145+
}
146+
147+
/**
148+
* Initializes a default set of {@linkplain HttpMessageConverter message converters}.
149+
*/
150+
private void initMessageConverters() {
151+
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>(4);
152+
153+
messageConverters.add(new ByteArrayHttpMessageConverter());
154+
155+
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
156+
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
157+
messageConverters.add(stringHttpMessageConverter);
158+
159+
try {
160+
messageConverters.add(new SourceHttpMessageConverter<>());
161+
}
162+
catch (Error err) {
163+
// Ignore when no TransformerFactory implementation is available
164+
}
165+
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
166+
167+
this.messageConverters = messageConverters;
168+
}
169+
170+
@Nullable
171+
@Override
172+
protected Object getHandlerInternal(@NotNull HttpServletRequest servletRequest) throws Exception {
173+
if (this.routerFunction != null) {
174+
ServerRequest request = ServerRequest.create(servletRequest, this.messageConverters);
175+
servletRequest.setAttribute(RouterFunctions.REQUEST_ATTRIBUTE, request);
176+
return this.routerFunction.route(request).orElse(null);
177+
}
178+
else {
179+
return null;
180+
}
181+
}
182+
183+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Classes supporting the {@code org.springframework.web.servlet.function} package.
19+
* Contains a {@code HandlerAdapter} that supports {@code HandlerFunction}s,
20+
* and a {@code HandlerMapping} that supports {@code RouterFunction}s.
21+
*/
22+
@NonNullApi
23+
@NonNullFields
24+
package org.springframework.web.servlet.function.support;
25+
26+
import org.springframework.lang.NonNullApi;
27+
import org.springframework.lang.NonNullFields;

spring-webmvc/src/main/resources/org/springframework/web/servlet/DispatcherServlet.properties

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i
77
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
88

99
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
10-
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
10+
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
11+
org.springframework.web.servlet.function.support.RouterFunctionMapping
1112

1213
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
1314
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
14-
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
15+
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
16+
org.springframework.web.servlet.function.support.HandlerFunctionAdapter
17+
1518

1619
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
1720
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\

0 commit comments

Comments
 (0)