Skip to content

Commit d87aa40

Browse files
committed
Add BindingContext
This commit adds a BindingContext to be used in spring-web-reactive @RequestMapping infrastructure (comparable to WebDataBinderFactory in spring-web-mvc) for access to the default model, data binding, validation, and type conversion purposes. Issue: SPR-14541
1 parent cabb253 commit d87aa40

26 files changed

+277
-132
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2002-2016 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+
package org.springframework.web.reactive.result.method;
17+
18+
import reactor.core.publisher.Mono;
19+
20+
import org.springframework.ui.ModelMap;
21+
import org.springframework.validation.support.BindingAwareModelMap;
22+
import org.springframework.web.bind.WebDataBinder;
23+
import org.springframework.web.bind.WebExchangeDataBinder;
24+
import org.springframework.web.bind.support.WebBindingInitializer;
25+
import org.springframework.web.server.ServerWebExchange;
26+
27+
/**
28+
* A context for binding requests to method arguments that provides access to
29+
* the default model, data binding, validation, and type conversion.
30+
*
31+
* @author Rossen Stoyanchev
32+
* @since 5.0
33+
*/
34+
public class BindingContext {
35+
36+
private final ModelMap model = new BindingAwareModelMap();
37+
38+
private final WebBindingInitializer initializer;
39+
40+
41+
public BindingContext() {
42+
this(null);
43+
}
44+
45+
public BindingContext(WebBindingInitializer initializer) {
46+
this.initializer = initializer;
47+
}
48+
49+
50+
/**
51+
* Return the default model.
52+
*/
53+
public ModelMap getModel() {
54+
return this.model;
55+
}
56+
57+
/**
58+
* Create a {@link WebExchangeDataBinder} for the given object.
59+
* @param exchange the current exchange
60+
* @param target the object to create a data binder for, or {@code null} if
61+
* creating a binder for a simple type
62+
* @param objectName the name of the target object
63+
* @return a Mono for the created {@link WebDataBinder} instance
64+
*/
65+
public Mono<WebExchangeDataBinder> createBinder(ServerWebExchange exchange, Object target,
66+
String objectName) {
67+
68+
WebExchangeDataBinder dataBinder = createBinderInstance(target, objectName);
69+
if (this.initializer != null) {
70+
this.initializer.initBinder(dataBinder);
71+
}
72+
return initBinder(dataBinder, exchange);
73+
}
74+
75+
protected WebExchangeDataBinder createBinderInstance(Object target, String objectName) {
76+
return new WebExchangeDataBinder(target, objectName);
77+
}
78+
79+
protected Mono<WebExchangeDataBinder> initBinder(WebExchangeDataBinder dataBinder, ServerWebExchange exchange) {
80+
return Mono.just(dataBinder);
81+
}
82+
83+
}

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/HandlerMethodArgumentResolver.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
import reactor.core.publisher.Mono;
2020

2121
import org.springframework.core.MethodParameter;
22-
import org.springframework.ui.ModelMap;
2322
import org.springframework.web.server.ServerWebExchange;
2423

2524

2625
/**
26+
* Strategy interface for resolving method parameters into argument values in
27+
* the context of a given request.
28+
*
2729
* @author Rossen Stoyanchev
2830
* @since 5.0
2931
*/
@@ -37,9 +39,10 @@ public interface HandlerMethodArgumentResolver {
3739
* does not resolve to any value, which will result in {@code null} passed
3840
* as the argument value.
3941
* @param parameter the method parameter
40-
* @param model the implicit model for request handling
42+
* @param bindingContext the binding context to use
4143
* @param exchange the current exchange
4244
*/
43-
Mono<Object> resolveArgument(MethodParameter parameter, ModelMap model, ServerWebExchange exchange);
45+
Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
46+
ServerWebExchange exchange);
4447

4548
}

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,18 +81,19 @@ protected Method getBridgedMethod() {
8181
/**
8282
* Invoke the method and return a Publisher for the return value.
8383
* @param exchange the current exchange
84-
* @param model the model for request handling
84+
* @param bindingContext the binding context to use
8585
* @param providedArgs optional list of argument values to check by type
8686
* (via {@code instanceof}) for resolving method arguments.
8787
* @return Publisher that produces a single HandlerResult or an error signal;
8888
* never throws an exception
8989
*/
90-
public Mono<HandlerResult> invokeForRequest(ServerWebExchange exchange, ModelMap model,
91-
Object... providedArgs) {
90+
public Mono<HandlerResult> invokeForRequest(ServerWebExchange exchange,
91+
BindingContext bindingContext, Object... providedArgs) {
9292

93-
return resolveArguments(exchange, model, providedArgs).then(args -> {
93+
return resolveArguments(exchange, bindingContext, providedArgs).then(args -> {
9494
try {
9595
Object value = doInvoke(args);
96+
ModelMap model = bindingContext.getModel();
9697
HandlerResult handlerResult = new HandlerResult(this, value, getReturnType(), model);
9798
return Mono.just(handlerResult);
9899
}
@@ -106,7 +107,9 @@ public Mono<HandlerResult> invokeForRequest(ServerWebExchange exchange, ModelMap
106107
});
107108
}
108109

109-
private Mono<Object[]> resolveArguments(ServerWebExchange exchange, ModelMap model, Object... providedArgs) {
110+
private Mono<Object[]> resolveArguments(ServerWebExchange exchange,
111+
BindingContext bindingContext, Object... providedArgs) {
112+
110113
if (ObjectUtils.isEmpty(getMethodParameters())) {
111114
return NO_ARGS;
112115
}
@@ -127,7 +130,7 @@ private Mono<Object[]> resolveArguments(ServerWebExchange exchange, ModelMap mod
127130
.findFirst()
128131
.orElseThrow(() -> getArgError("No resolver for ", param, null));
129132
try {
130-
return resolver.resolveArgument(param, model, exchange)
133+
return resolver.resolveArgument(param, bindingContext, exchange)
131134
.defaultIfEmpty(NO_VALUE)
132135
.doOnError(cause -> {
133136
if(logger.isDebugEnabled()) {

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueMethodArgumentResolver.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.ui.ModelMap;
3333
import org.springframework.util.Assert;
3434
import org.springframework.web.bind.annotation.ValueConstants;
35+
import org.springframework.web.reactive.result.method.BindingContext;
3536
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
3637
import org.springframework.web.server.ServerErrorException;
3738
import org.springframework.web.server.ServerWebExchange;
@@ -85,7 +86,9 @@ public AbstractNamedValueMethodArgumentResolver(ConversionService conversionServ
8586

8687

8788
@Override
88-
public Mono<Object> resolveArgument(MethodParameter parameter, ModelMap model, ServerWebExchange exchange) {
89+
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
90+
ServerWebExchange exchange) {
91+
8992
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
9093
MethodParameter nestedParameter = parameter.nestedIfOptional();
9194

@@ -95,6 +98,8 @@ public Mono<Object> resolveArgument(MethodParameter parameter, ModelMap model, S
9598
"Specified name must not resolve to null: [" + namedValueInfo.name + "]"));
9699
}
97100

101+
ModelMap model = bindingContext.getModel();
102+
98103
return resolveName(resolvedName.toString(), nestedParameter, exchange)
99104
.map(arg -> {
100105
if ("".equals(arg) && namedValueInfo.defaultValue != null) {

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolver.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
import org.springframework.http.RequestEntity;
2828
import org.springframework.http.codec.HttpMessageReader;
2929
import org.springframework.http.server.reactive.ServerHttpRequest;
30-
import org.springframework.ui.ModelMap;
3130
import org.springframework.validation.Validator;
31+
import org.springframework.web.reactive.result.method.BindingContext;
3232
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
3333
import org.springframework.web.server.ServerWebExchange;
3434

@@ -73,7 +73,8 @@ public boolean supportsParameter(MethodParameter parameter) {
7373
}
7474

7575
@Override
76-
public Mono<Object> resolveArgument(MethodParameter param, ModelMap model, ServerWebExchange exchange) {
76+
public Mono<Object> resolveArgument(MethodParameter param, BindingContext bindingContext,
77+
ServerWebExchange exchange) {
7778

7879
ResolvableType entityType = ResolvableType.forMethodParameter(param);
7980
MethodParameter bodyParameter = new MethodParameter(param);

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelArgumentResolver.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
import org.springframework.core.MethodParameter;
2121
import org.springframework.ui.Model;
22-
import org.springframework.ui.ModelMap;
22+
import org.springframework.web.reactive.result.method.BindingContext;
2323
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
2424
import org.springframework.web.server.ServerWebExchange;
2525

@@ -38,8 +38,10 @@ public boolean supportsParameter(MethodParameter parameter) {
3838
}
3939

4040
@Override
41-
public Mono<Object> resolveArgument(MethodParameter parameter, ModelMap model, ServerWebExchange exchange) {
42-
return Mono.just(model);
41+
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
42+
ServerWebExchange exchange) {
43+
44+
return Mono.just(bindingContext.getModel());
4345
}
4446

4547
}

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMapMethodArgumentResolver.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323
import reactor.core.publisher.Mono;
2424

2525
import org.springframework.core.MethodParameter;
26-
import org.springframework.ui.ModelMap;
2726
import org.springframework.util.StringUtils;
2827
import org.springframework.web.bind.annotation.PathVariable;
2928
import org.springframework.web.reactive.HandlerMapping;
29+
import org.springframework.web.reactive.result.method.BindingContext;
3030
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
3131
import org.springframework.web.server.ServerWebExchange;
3232

@@ -54,7 +54,7 @@ public boolean supportsParameter(MethodParameter parameter) {
5454
* Return a Map with all URI template variables or an empty map.
5555
*/
5656
@Override
57-
public Mono<Object> resolveArgument(MethodParameter parameter, ModelMap model,
57+
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
5858
ServerWebExchange exchange) {
5959

6060
String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
import org.springframework.core.MethodParameter;
2424
import org.springframework.core.ReactiveAdapterRegistry;
2525
import org.springframework.http.codec.HttpMessageReader;
26-
import org.springframework.ui.ModelMap;
2726
import org.springframework.validation.Validator;
2827
import org.springframework.web.bind.annotation.RequestBody;
28+
import org.springframework.web.reactive.result.method.BindingContext;
2929
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
3030
import org.springframework.web.server.ServerWebExchange;
3131
import org.springframework.web.server.ServerWebInputException;
@@ -76,7 +76,9 @@ public boolean supportsParameter(MethodParameter parameter) {
7676
}
7777

7878
@Override
79-
public Mono<Object> resolveArgument(MethodParameter param, ModelMap model, ServerWebExchange exchange) {
79+
public Mono<Object> resolveArgument(MethodParameter param, BindingContext bindingContext,
80+
ServerWebExchange exchange) {
81+
8082
boolean isRequired = param.getParameterAnnotation(RequestBody.class).required();
8183
return readBody(param, isRequired, exchange);
8284
}

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMapMethodArgumentResolver.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222

2323
import org.springframework.core.MethodParameter;
2424
import org.springframework.http.HttpHeaders;
25-
import org.springframework.ui.ModelMap;
2625
import org.springframework.util.MultiValueMap;
2726
import org.springframework.web.bind.annotation.RequestHeader;
27+
import org.springframework.web.reactive.result.method.BindingContext;
2828
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
2929
import org.springframework.web.server.ServerWebExchange;
3030

@@ -50,7 +50,9 @@ public boolean supportsParameter(MethodParameter parameter) {
5050
}
5151

5252
@Override
53-
public Mono<Object> resolveArgument(MethodParameter parameter, ModelMap model, ServerWebExchange exchange) {
53+
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
54+
ServerWebExchange exchange) {
55+
5456
HttpHeaders headers = exchange.getRequest().getHeaders();
5557
if (MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) {
5658
return Mono.just(headers);

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,13 @@
3838
import org.springframework.format.support.DefaultFormattingConversionService;
3939
import org.springframework.http.codec.DecoderHttpMessageReader;
4040
import org.springframework.http.codec.HttpMessageReader;
41-
import org.springframework.ui.ExtendedModelMap;
42-
import org.springframework.ui.ModelMap;
4341
import org.springframework.validation.Validator;
4442
import org.springframework.web.bind.support.WebBindingInitializer;
4543
import org.springframework.web.method.HandlerMethod;
4644
import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver;
4745
import org.springframework.web.reactive.HandlerAdapter;
4846
import org.springframework.web.reactive.HandlerResult;
47+
import org.springframework.web.reactive.result.method.BindingContext;
4948
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
5049
import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
5150
import org.springframework.web.server.ServerWebExchange;
@@ -257,14 +256,16 @@ public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
257256
HandlerMethod handlerMethod = (HandlerMethod) handler;
258257
InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod);
259258
invocable.setHandlerMethodArgumentResolvers(getArgumentResolvers());
260-
ModelMap model = new ExtendedModelMap();
261-
return invocable.invokeForRequest(exchange, model)
262-
.map(result -> result.setExceptionHandler(ex -> handleException(ex, handlerMethod, exchange)))
263-
.otherwise(ex -> handleException(ex, handlerMethod, exchange));
259+
BindingContext bindingContext = new BindingContext(getWebBindingInitializer());
260+
return invocable.invokeForRequest(exchange, bindingContext)
261+
.map(result -> result.setExceptionHandler(
262+
ex -> handleException(ex, handlerMethod, bindingContext, exchange)))
263+
.otherwise(ex -> handleException(
264+
ex, handlerMethod, bindingContext, exchange));
264265
}
265266

266267
private Mono<HandlerResult> handleException(Throwable ex, HandlerMethod handlerMethod,
267-
ServerWebExchange exchange) {
268+
BindingContext bindingContext, ServerWebExchange exchange) {
268269

269270
if (ex instanceof Exception) {
270271
InvocableHandlerMethod invocable = findExceptionHandler(handlerMethod, (Exception) ex);
@@ -274,8 +275,8 @@ private Mono<HandlerResult> handleException(Throwable ex, HandlerMethod handlerM
274275
logger.debug("Invoking @ExceptionHandler method: " + invocable);
275276
}
276277
invocable.setHandlerMethodArgumentResolvers(getArgumentResolvers());
277-
ExtendedModelMap errorModel = new ExtendedModelMap();
278-
return invocable.invokeForRequest(exchange, errorModel, ex);
278+
bindingContext.getModel().clear();
279+
return invocable.invokeForRequest(exchange, bindingContext, ex);
279280
}
280281
catch (Exception invocationEx) {
281282
if (logger.isErrorEnabled()) {

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolver.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
import reactor.core.publisher.Mono;
2222

2323
import org.springframework.core.MethodParameter;
24-
import org.springframework.ui.ModelMap;
2524
import org.springframework.util.MultiValueMap;
2625
import org.springframework.util.StringUtils;
2726
import org.springframework.web.bind.annotation.RequestParam;
27+
import org.springframework.web.reactive.result.method.BindingContext;
2828
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
2929
import org.springframework.web.server.ServerWebExchange;
3030

@@ -57,7 +57,9 @@ public boolean supportsParameter(MethodParameter parameter) {
5757
}
5858

5959
@Override
60-
public Mono<Object> resolveArgument(MethodParameter parameter, ModelMap model, ServerWebExchange exchange) {
60+
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
61+
ServerWebExchange exchange) {
62+
6163
Class<?> paramType = parameter.getParameterType();
6264
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
6365
if (MultiValueMap.class.isAssignableFrom(paramType)) {

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ServerWebExchangeArgumentResolver.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import org.springframework.http.HttpMethod;
2222
import org.springframework.http.server.reactive.ServerHttpRequest;
2323
import org.springframework.http.server.reactive.ServerHttpResponse;
24-
import org.springframework.ui.ModelMap;
24+
import org.springframework.web.reactive.result.method.BindingContext;
2525
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
2626
import org.springframework.web.server.ServerWebExchange;
2727
import org.springframework.web.server.WebSession;
@@ -52,7 +52,9 @@ public boolean supportsParameter(MethodParameter parameter) {
5252
}
5353

5454
@Override
55-
public Mono<Object> resolveArgument(MethodParameter parameter, ModelMap model, ServerWebExchange exchange) {
55+
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
56+
ServerWebExchange exchange) {
57+
5658
Class<?> paramType = parameter.getParameterType();
5759
if (ServerWebExchange.class.isAssignableFrom(paramType)) {
5860
return Mono.just(exchange);

0 commit comments

Comments
 (0)