Skip to content

Commit 01e3561

Browse files
committed
Support RouterFunction in @EnableWebFlux
This commit removes the RouterFunctions.toHandlerMapping method, in favor of native support for RouterFunctions in @EnableWebFlux configuration classes. In order to accomplish this, the HandlerStrategies components has been repurposed to only be used for the "bare-bones" HttpHandler, while the (newly introduced) RouterFunctionMapping uses the strategies as exposed through WebFluxConfigurationSupport. Furthermore, this commit also introduces support for testing RouterFunctions without resorting to an application context. Issue: SPR-15536
1 parent 3d29016 commit 01e3561

21 files changed

+647
-247
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2002-2017 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.test.web.reactive.server;
18+
19+
import org.springframework.web.reactive.function.server.HandlerStrategies;
20+
import org.springframework.web.reactive.function.server.RouterFunction;
21+
import org.springframework.web.reactive.function.server.RouterFunctions;
22+
import org.springframework.web.server.WebHandler;
23+
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
24+
25+
/**
26+
* Spec for setting up server-less testing against a RouterFunction.
27+
*
28+
* @author Arjen Poutsma
29+
* @since 5.0
30+
*/
31+
class DefaultRouterFunctionSpec extends AbstractMockServerSpec<WebTestClient.RouterFunctionSpec>
32+
implements WebTestClient.RouterFunctionSpec {
33+
34+
private final RouterFunction<?> routerFunction;
35+
36+
private HandlerStrategies handlerStrategies = HandlerStrategies.withDefaults();
37+
38+
39+
DefaultRouterFunctionSpec(RouterFunction<?> routerFunction) {
40+
this.routerFunction = routerFunction;
41+
}
42+
43+
44+
@Override
45+
public WebTestClient.RouterFunctionSpec handlerStrategies(HandlerStrategies handlerStrategies) {
46+
if (handlerStrategies != null) {
47+
this.handlerStrategies = handlerStrategies;
48+
}
49+
return this;
50+
}
51+
52+
@Override
53+
protected WebHttpHandlerBuilder initHttpHandlerBuilder() {
54+
WebHandler webHandler = RouterFunctions.toWebHandler(this.routerFunction, this.handlerStrategies);
55+
return WebHttpHandlerBuilder.webHandler(webHandler);
56+
}
57+
58+
}

spring-test/src/main/java/org/springframework/test/web/reactive/server/RouterFunctionSpec.java

Lines changed: 0 additions & 62 deletions
This file was deleted.

spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.springframework.web.reactive.function.client.ExchangeFunction;
4949
import org.springframework.web.reactive.function.client.ExchangeStrategies;
5050
import org.springframework.web.reactive.function.client.WebClient;
51+
import org.springframework.web.reactive.function.server.HandlerStrategies;
5152
import org.springframework.web.reactive.function.server.RouterFunction;
5253
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
5354
import org.springframework.web.server.ServerWebExchange;
@@ -169,8 +170,8 @@ static MockServerSpec<?> bindToApplicationContext(ApplicationContext application
169170
* @param routerFunction the RouterFunction to test
170171
* @return the {@link WebTestClient} builder
171172
*/
172-
static MockServerSpec<?> bindToRouterFunction(RouterFunction<?> routerFunction) {
173-
return new RouterFunctionSpec(routerFunction);
173+
static RouterFunctionSpec bindToRouterFunction(RouterFunction<?> routerFunction) {
174+
return new DefaultRouterFunctionSpec(routerFunction);
174175
}
175176

176177
/**
@@ -285,6 +286,18 @@ interface ControllerSpec extends MockServerSpec<ControllerSpec> {
285286

286287
}
287288

289+
/**
290+
* Specification for customizing router function configuration.
291+
*/
292+
interface RouterFunctionSpec extends MockServerSpec<RouterFunctionSpec> {
293+
294+
/**
295+
* Configure handler strategies.
296+
*/
297+
RouterFunctionSpec handlerStrategies(HandlerStrategies handlerStrategies);
298+
299+
}
300+
288301
/**
289302
* Steps for customizing the {@link WebClient} used to test with
290303
* internally delegating to a {@link WebClient.Builder}.

spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@
4747
import org.springframework.web.reactive.HandlerMapping;
4848
import org.springframework.web.reactive.accept.CompositeContentTypeResolver;
4949
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
50+
import org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter;
51+
import org.springframework.web.reactive.function.server.support.RouterFunctionMapping;
52+
import org.springframework.web.reactive.function.server.support.ServerResponseResultHandler;
5053
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
5154
import org.springframework.web.reactive.result.SimpleHandlerAdapter;
5255
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
@@ -81,6 +84,8 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
8184

8285
private PathMatchConfigurer pathMatchConfigurer;
8386

87+
private ViewResolverRegistry viewResolverRegistry;
88+
8489
private ApplicationContext applicationContext;
8590

8691

@@ -203,6 +208,23 @@ protected final PathMatchConfigurer getPathMatchConfigurer() {
203208
public void configurePathMatching(PathMatchConfigurer configurer) {
204209
}
205210

211+
@Bean
212+
public RouterFunctionMapping routerFunctionMapping() {
213+
RouterFunctionMapping mapping = createRouterFunctionMapping();
214+
mapping.setOrder(-1); // go before RequestMappingHandlerMapping
215+
mapping.setMessageCodecConfigurer(serverCodecConfigurer());
216+
mapping.setCorsConfigurations(getCorsConfigurations());
217+
218+
return mapping;
219+
}
220+
221+
/**
222+
* Override to plug a sub-class of {@link RouterFunctionMapping}.
223+
*/
224+
protected RouterFunctionMapping createRouterFunctionMapping() {
225+
return new RouterFunctionMapping();
226+
}
227+
206228
/**
207229
* Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped
208230
* resource handlers. To configure resource handling, override
@@ -362,6 +384,11 @@ protected MessageCodesResolver getMessageCodesResolver() {
362384
return null;
363385
}
364386

387+
@Bean
388+
public HandlerFunctionAdapter handlerFunctionAdapter() {
389+
return new HandlerFunctionAdapter();
390+
}
391+
365392
@Bean
366393
public SimpleHandlerAdapter simpleHandlerAdapter() {
367394
return new SimpleHandlerAdapter();
@@ -381,15 +408,38 @@ public ResponseBodyResultHandler responseBodyResultHandler() {
381408

382409
@Bean
383410
public ViewResolutionResultHandler viewResolutionResultHandler() {
384-
ViewResolverRegistry registry = new ViewResolverRegistry(getApplicationContext());
385-
configureViewResolvers(registry);
411+
ViewResolverRegistry registry = getViewResolverRegistry();
386412
List<ViewResolver> resolvers = registry.getViewResolvers();
387413
ViewResolutionResultHandler handler = new ViewResolutionResultHandler(
388414
resolvers, webFluxContentTypeResolver(), webFluxAdapterRegistry());
389415
handler.setDefaultViews(registry.getDefaultViews());
390416
handler.setOrder(registry.getOrder());
391417
return handler;
418+
}
419+
420+
@Bean
421+
public ServerResponseResultHandler serverResponseResultHandler() {
422+
ViewResolverRegistry registry = getViewResolverRegistry();
423+
List<ViewResolver> resolvers = registry.getViewResolvers();
424+
425+
ServerResponseResultHandler handler = new ServerResponseResultHandler();
426+
handler.setMessageCodecConfigurer(serverCodecConfigurer());
427+
handler.setViewResolvers(resolvers);
428+
handler.setOrder(registry.getOrder() + 1);
429+
430+
return handler;
431+
}
392432

433+
/**
434+
* Callback for building the {@link ViewResolverRegistry}. This method is final,
435+
* use {@link #configureViewResolvers} to customize view resolvers.
436+
*/
437+
protected final ViewResolverRegistry getViewResolverRegistry() {
438+
if (this.viewResolverRegistry == null) {
439+
this.viewResolverRegistry = new ViewResolverRegistry(getApplicationContext());
440+
configureViewResolvers(this.viewResolverRegistry);
441+
}
442+
return this.viewResolverRegistry;
393443
}
394444

395445
/**

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,13 +203,13 @@ public T entity() {
203203
}
204204

205205
@Override
206-
public Mono<Void> writeTo(ServerWebExchange exchange, HandlerStrategies strategies) {
206+
public Mono<Void> writeTo(ServerWebExchange exchange, Context context) {
207207
ServerHttpResponse response = exchange.getResponse();
208208
writeStatusAndHeaders(response);
209209
return inserter().insert(response, new BodyInserter.Context() {
210210
@Override
211211
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
212-
return strategies.messageWriters();
212+
return context.messageWriters();
213213
}
214214

215215
@Override

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultHandlerStrategiesBuilder.java

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@
1919
import java.util.ArrayList;
2020
import java.util.Collections;
2121
import java.util.List;
22-
import java.util.Locale;
23-
import java.util.Optional;
2422
import java.util.function.Consumer;
25-
import java.util.function.Function;
2623
import java.util.function.Supplier;
2724
import java.util.stream.Stream;
2825

@@ -44,18 +41,10 @@
4441
*/
4542
class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
4643

47-
static final Function<ServerRequest, Optional<Locale>> DEFAULT_LOCALE_RESOLVER =
48-
request -> request.headers().acceptLanguage().stream()
49-
.map(Locale.LanguageRange::getRange)
50-
.map(Locale::forLanguageTag).findFirst();
51-
52-
5344
private final ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create();
5445

5546
private final List<ViewResolver> viewResolvers = new ArrayList<>();
5647

57-
private Function<ServerRequest, Optional<Locale>> localeResolver;
58-
5948
private final List<WebFilter> webFilters = new ArrayList<>();
6049

6150
private final List<WebExceptionHandler> exceptionHandlers = new ArrayList<>();
@@ -67,7 +56,6 @@ public DefaultHandlerStrategiesBuilder() {
6756

6857
public void defaultConfiguration() {
6958
this.codecConfigurer.registerDefaults(true);
70-
localeResolver(DEFAULT_LOCALE_RESOLVER);
7159
exceptionHandler(new ResponseStatusExceptionHandler());
7260
}
7361

@@ -94,13 +82,6 @@ public HandlerStrategies.Builder viewResolver(ViewResolver viewResolver) {
9482
return this;
9583
}
9684

97-
@Override
98-
public HandlerStrategies.Builder localeResolver(Function<ServerRequest, Optional<Locale>> localeResolver) {
99-
Assert.notNull(localeResolver, "'localeResolver' must not be null");
100-
this.localeResolver = localeResolver;
101-
return this;
102-
}
103-
10485
@Override
10586
public HandlerStrategies.Builder webFilter(WebFilter filter) {
10687
Assert.notNull(filter, "'filter' must not be null");
@@ -118,8 +99,8 @@ public HandlerStrategies.Builder exceptionHandler(WebExceptionHandler exceptionH
11899
@Override
119100
public HandlerStrategies build() {
120101
return new DefaultHandlerStrategies(this.codecConfigurer.getReaders(),
121-
this.codecConfigurer.getWriters(), this.viewResolvers, this.localeResolver,
122-
this.webFilters, this.exceptionHandlers);
102+
this.codecConfigurer.getWriters(), this.viewResolvers, this.webFilters,
103+
this.exceptionHandlers);
123104
}
124105

125106

@@ -131,8 +112,6 @@ private static class DefaultHandlerStrategies implements HandlerStrategies {
131112

132113
private final List<ViewResolver> viewResolvers;
133114

134-
private final Function<ServerRequest, Optional<Locale>> localeResolver;
135-
136115
private final List<WebFilter> webFilters;
137116

138117
private final List<WebExceptionHandler> exceptionHandlers;
@@ -141,14 +120,12 @@ public DefaultHandlerStrategies(
141120
List<HttpMessageReader<?>> messageReaders,
142121
List<HttpMessageWriter<?>> messageWriters,
143122
List<ViewResolver> viewResolvers,
144-
Function<ServerRequest, Optional<Locale>> localeResolver,
145123
List<WebFilter> webFilters,
146124
List<WebExceptionHandler> exceptionHandlers) {
147125

148126
this.messageReaders = unmodifiableCopy(messageReaders);
149127
this.messageWriters = unmodifiableCopy(messageWriters);
150128
this.viewResolvers = unmodifiableCopy(viewResolvers);
151-
this.localeResolver = localeResolver;
152129
this.webFilters = unmodifiableCopy(webFilters);
153130
this.exceptionHandlers = unmodifiableCopy(exceptionHandlers);
154131
}
@@ -172,11 +149,6 @@ public Supplier<Stream<ViewResolver>> viewResolvers() {
172149
return this.viewResolvers::stream;
173150
}
174151

175-
@Override
176-
public Supplier<Function<ServerRequest, Optional<Locale>>> localeResolver() {
177-
return () -> this.localeResolver;
178-
}
179-
180152
@Override
181153
public Supplier<Stream<WebFilter>> webFilters() {
182154
return this.webFilters::stream;

0 commit comments

Comments
 (0)