Skip to content

Commit ae003e8

Browse files
committed
HandlerResult provides access to BindingContext
Issue: SPR-14542
1 parent 6abd4d5 commit ae003e8

File tree

4 files changed

+52
-45
lines changed

4 files changed

+52
-45
lines changed

spring-web-reactive/src/main/java/org/springframework/web/reactive/HandlerResult.java

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@
2323

2424
import org.springframework.core.MethodParameter;
2525
import org.springframework.core.ResolvableType;
26-
import org.springframework.ui.ConcurrentModel;
2726
import org.springframework.ui.Model;
2827
import org.springframework.util.Assert;
28+
import org.springframework.web.reactive.result.method.BindingContext;
2929

3030
/**
31-
* Represent the result of the invocation of a handler.
31+
* Represent the result of the invocation of a handler or a handler method.
3232
*
3333
* @author Rossen Stoyanchev
3434
* @since 5.0
@@ -37,12 +37,11 @@ public class HandlerResult {
3737

3838
private final Object handler;
3939

40-
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
41-
private final Optional<Object> returnValue;
40+
private final Object returnValue;
4241

4342
private final ResolvableType returnType;
4443

45-
private final Model model;
44+
private final BindingContext bindingContext;
4645

4746
private Function<Throwable, Mono<HandlerResult>> exceptionHandler;
4847

@@ -62,15 +61,17 @@ public HandlerResult(Object handler, Object returnValue, MethodParameter returnT
6261
* @param handler the handler that handled the request
6362
* @param returnValue the return value from the handler possibly {@code null}
6463
* @param returnType the return value type
65-
* @param model the model used for request handling
64+
* @param context the binding context used for request handling
6665
*/
67-
public HandlerResult(Object handler, Object returnValue, MethodParameter returnType, Model model) {
66+
public HandlerResult(Object handler, Object returnValue, MethodParameter returnType,
67+
BindingContext context) {
68+
6869
Assert.notNull(handler, "'handler' is required");
6970
Assert.notNull(returnType, "'returnType' is required");
7071
this.handler = handler;
71-
this.returnValue = Optional.ofNullable(returnValue);
72+
this.returnValue = returnValue;
7273
this.returnType = ResolvableType.forMethodParameter(returnType);
73-
this.model = (model != null ? model : new ConcurrentModel());
74+
this.bindingContext = (context != null ? context : new BindingContext());
7475
}
7576

7677

@@ -85,7 +86,7 @@ public Object getHandler() {
8586
* Return the value returned from the handler wrapped as {@link Optional}.
8687
*/
8788
public Optional<Object> getReturnValue() {
88-
return this.returnValue;
89+
return Optional.ofNullable(this.returnValue);
8990
}
9091

9192
/**
@@ -104,11 +105,18 @@ public MethodParameter getReturnTypeSource() {
104105
}
105106

106107
/**
107-
* Return the model used during request handling with attributes that may be
108-
* used to render HTML templates with.
108+
* Return the BindingContext used for request handling.
109+
*/
110+
public BindingContext getBindingContext() {
111+
return this.bindingContext;
112+
}
113+
114+
/**
115+
* Return the model used for request handling. This is a shortcut for
116+
* {@code getBindingContext().getModel()}.
109117
*/
110118
public Model getModel() {
111-
return this.model;
119+
return this.bindingContext.getModel();
112120
}
113121

114122
/**

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@
3232
import org.springframework.core.GenericTypeResolver;
3333
import org.springframework.core.MethodParameter;
3434
import org.springframework.core.ParameterNameDiscoverer;
35-
import org.springframework.ui.Model;
36-
import org.springframework.ui.ModelMap;
3735
import org.springframework.util.ClassUtils;
3836
import org.springframework.util.ObjectUtils;
3937
import org.springframework.util.ReflectionUtils;
@@ -102,9 +100,8 @@ public Mono<HandlerResult> invoke(ServerWebExchange exchange,
102100
return resolveArguments(exchange, bindingContext, providedArgs).then(args -> {
103101
try {
104102
Object value = doInvoke(args);
105-
Model model = bindingContext.getModel();
106-
HandlerResult handlerResult = new HandlerResult(this, value, getReturnType(), model);
107-
return Mono.just(handlerResult);
103+
HandlerResult result = new HandlerResult(this, value, getReturnType(), bindingContext);
104+
return Mono.just(result);
108105
}
109106
catch (InvocationTargetException ex) {
110107
return Mono.error(ex.getTargetException());

spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,10 @@
3737
import org.springframework.http.codec.ResourceHttpMessageWriter;
3838
import org.springframework.http.codec.json.Jackson2JsonEncoder;
3939
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
40+
import org.springframework.http.server.reactive.ServerHttpRequest;
4041
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
4142
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
42-
import org.springframework.http.server.reactive.ServerHttpRequest;
4343
import org.springframework.stereotype.Controller;
44-
import org.springframework.ui.ExtendedModelMap;
4544
import org.springframework.util.ObjectUtils;
4645
import org.springframework.web.bind.annotation.ResponseBody;
4746
import org.springframework.web.bind.annotation.ResponseStatus;
@@ -121,21 +120,21 @@ public void supports() throws NoSuchMethodException {
121120
public void writeResponseStatus() throws NoSuchMethodException {
122121
Object controller = new TestRestController();
123122
HandlerMethod hm = handlerMethod(controller, "handleToString");
124-
HandlerResult handlerResult = new HandlerResult(hm, null, hm.getReturnType(), new ExtendedModelMap());
123+
HandlerResult handlerResult = new HandlerResult(hm, null, hm.getReturnType());
125124

126125
StepVerifier.create(this.resultHandler.handleResult(this.exchange, handlerResult)).expectComplete().verify();
127126
assertEquals(HttpStatus.NO_CONTENT, this.response.getStatusCode());
128127

129128
hm = handlerMethod(controller, "handleToMonoVoid");
130-
handlerResult = new HandlerResult(hm, null, hm.getReturnType(), new ExtendedModelMap());
129+
handlerResult = new HandlerResult(hm, null, hm.getReturnType());
131130

132131
StepVerifier.create(this.resultHandler.handleResult(this.exchange, handlerResult)).expectComplete().verify();
133132
assertEquals(HttpStatus.CREATED, this.response.getStatusCode());
134133
}
135134

136135
private void testSupports(Object controller, String method, boolean result) throws NoSuchMethodException {
137136
HandlerMethod hm = handlerMethod(controller, method);
138-
HandlerResult handlerResult = new HandlerResult(hm, null, hm.getReturnType(), new ExtendedModelMap());
137+
HandlerResult handlerResult = new HandlerResult(hm, null, hm.getReturnType());
139138
assertEquals(result, this.resultHandler.supports(handlerResult));
140139
}
141140

spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.List;
2626
import java.util.Locale;
2727
import java.util.Map;
28+
import java.util.TreeMap;
2829

2930
import org.junit.Before;
3031
import org.junit.Test;
@@ -45,14 +46,15 @@
4546
import org.springframework.http.server.reactive.ServerHttpResponse;
4647
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
4748
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
48-
import org.springframework.ui.ExtendedModelMap;
49+
import org.springframework.ui.ConcurrentModel;
4950
import org.springframework.ui.Model;
5051
import org.springframework.web.bind.annotation.ModelAttribute;
5152
import org.springframework.web.bind.annotation.ResponseStatus;
5253
import org.springframework.web.reactive.HandlerResult;
5354
import org.springframework.web.reactive.accept.HeaderContentTypeResolver;
5455
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
5556
import org.springframework.web.reactive.result.ResolvableMethod;
57+
import org.springframework.web.reactive.result.method.BindingContext;
5658
import org.springframework.web.server.NotAcceptableStatusException;
5759
import org.springframework.web.server.ServerWebExchange;
5860
import org.springframework.web.server.adapter.DefaultServerWebExchange;
@@ -61,7 +63,6 @@
6163

6264
import static java.nio.charset.StandardCharsets.UTF_8;
6365
import static org.junit.Assert.assertEquals;
64-
import static org.junit.Assert.assertNotNull;
6566
import static org.mockito.Mockito.mock;
6667
import static org.springframework.core.ResolvableType.forClass;
6768
import static org.springframework.core.ResolvableType.forClassWithGenerics;
@@ -80,7 +81,7 @@ public class ViewResolutionResultHandlerTests {
8081

8182
private ServerWebExchange exchange;
8283

83-
private Model model = new ExtendedModelMap();
84+
private final BindingContext bindingContext = new BindingContext();
8485

8586

8687
@Before
@@ -114,7 +115,7 @@ private void testSupports(ResolvableType type, boolean result) {
114115
private void testSupports(ResolvableMethod resolvableMethod, boolean result) {
115116
ViewResolutionResultHandler resultHandler = resultHandler(mock(ViewResolver.class));
116117
MethodParameter returnType = resolvableMethod.resolveReturnType();
117-
HandlerResult handlerResult = new HandlerResult(new Object(), null, returnType, this.model);
118+
HandlerResult handlerResult = new HandlerResult(new Object(), null, returnType, this.bindingContext);
118119
assertEquals(result, resultHandler.supports(handlerResult));
119120
}
120121

@@ -156,7 +157,7 @@ public void handleReturnValueTypes() throws Exception {
156157
assertEquals(HttpStatus.PARTIAL_CONTENT, this.exchange.getResponse().getStatusCode());
157158

158159
returnType = forClass(Model.class);
159-
returnValue = new ExtendedModelMap().addAttribute("name", "Joe");
160+
returnValue = new ConcurrentModel().addAttribute("name", "Joe");
160161
testHandle("/account", returnType, returnValue, "account: {id=123, name=Joe}", resolver);
161162

162163
returnType = forClass(Map.class);
@@ -190,8 +191,8 @@ public void defaultViewName() throws Exception {
190191
}
191192

192193
private void testDefaultViewName(Object returnValue, ResolvableType type) throws URISyntaxException {
193-
Model model = new ExtendedModelMap().addAttribute("id", "123");
194-
HandlerResult result = new HandlerResult(new Object(), returnValue, returnType(type), model);
194+
this.bindingContext.getModel().addAttribute("id", "123");
195+
HandlerResult result = new HandlerResult(new Object(), returnValue, returnType(type), this.bindingContext);
195196
ViewResolutionResultHandler handler = resultHandler(new TestViewResolver("account"));
196197

197198
this.request.setUri("/account");
@@ -210,8 +211,8 @@ private void testDefaultViewName(Object returnValue, ResolvableType type) throws
210211
@Test
211212
public void unresolvedViewName() throws Exception {
212213
String returnValue = "account";
213-
ResolvableType type = forClass(String.class);
214-
HandlerResult result = new HandlerResult(new Object(), returnValue, returnType(type), this.model);
214+
MethodParameter returnType = returnType(forClass(String.class));
215+
HandlerResult result = new HandlerResult(new Object(), returnValue, returnType, this.bindingContext);
215216

216217
this.request.setUri("/path");
217218
Mono<Void> mono = resultHandler().handleResult(this.exchange, result);
@@ -225,8 +226,8 @@ public void unresolvedViewName() throws Exception {
225226
@Test
226227
public void contentNegotiation() throws Exception {
227228
TestBean value = new TestBean("Joe");
228-
ResolvableType type = forClass(TestBean.class);
229-
HandlerResult handlerResult = new HandlerResult(new Object(), value, returnType(type), this.model);
229+
MethodParameter returnType = returnType(forClass(TestBean.class));
230+
HandlerResult handlerResult = new HandlerResult(new Object(), value, returnType, this.bindingContext);
230231

231232
this.request.setHeader("Accept", "application/json");
232233
this.request.setUri("/account");
@@ -244,8 +245,8 @@ public void contentNegotiation() throws Exception {
244245
@Test
245246
public void contentNegotiationWith406() throws Exception {
246247
TestBean value = new TestBean("Joe");
247-
ResolvableType type = forClass(TestBean.class);
248-
HandlerResult handlerResult = new HandlerResult(new Object(), value, returnType(type), this.model);
248+
MethodParameter returnType = returnType(forClass(TestBean.class));
249+
HandlerResult handlerResult = new HandlerResult(new Object(), value, returnType, this.bindingContext);
249250

250251
this.request.setHeader("Accept", "application/json");
251252
this.request.setUri("/account");
@@ -260,13 +261,13 @@ public void contentNegotiationWith406() throws Exception {
260261

261262
@Test
262263
public void modelWithAsyncAttributes() throws Exception {
263-
Model model = new ExtendedModelMap();
264-
model.addAttribute("bean1", Mono.just(new TestBean("Bean1")));
265-
model.addAttribute("bean2", Single.just(new TestBean("Bean2")));
266-
model.addAttribute("empty", Mono.empty());
264+
this.bindingContext.getModel()
265+
.addAttribute("bean1", Mono.just(new TestBean("Bean1")))
266+
.addAttribute("bean2", Single.just(new TestBean("Bean2")))
267+
.addAttribute("empty", Mono.empty());
267268

268269
ResolvableType type = forClass(void.class);
269-
HandlerResult result = new HandlerResult(new Object(), null, returnType(type), model);
270+
HandlerResult result = new HandlerResult(new Object(), null, returnType(type), this.bindingContext);
270271
ViewResolutionResultHandler handler = resultHandler(new TestViewResolver("account"));
271272

272273
this.request.setUri("/account");
@@ -304,9 +305,11 @@ private void testHandle(String path, ResolvableType returnType, Object returnVal
304305
private void testHandle(String path, ResolvableMethod resolvableMethod, Object returnValue,
305306
String responseBody, ViewResolver... resolvers) throws URISyntaxException {
306307

307-
Model model = new ExtendedModelMap().addAttribute("id", "123");
308+
Model model = this.bindingContext.getModel();
309+
model.asMap().clear();
310+
model.addAttribute("id", "123");
308311
MethodParameter returnType = resolvableMethod.resolveReturnType();
309-
HandlerResult result = new HandlerResult(new Object(), returnValue, returnType, model);
312+
HandlerResult result = new HandlerResult(new Object(), returnValue, returnType, this.bindingContext);
310313
this.request.setUri(path);
311314
resultHandler(resolvers).handleResult(this.exchange, result).block(Duration.ofSeconds(5));
312315
assertResponseBody(responseBody);
@@ -375,12 +378,12 @@ public List<MediaType> getSupportedMediaTypes() {
375378

376379
@Override
377380
public Mono<Void> render(Map<String, ?> model, MediaType mediaType, ServerWebExchange exchange) {
378-
String value = this.name + ": " + model.toString();
379-
assertNotNull(value);
380381
ServerHttpResponse response = exchange.getResponse();
381382
if (mediaType != null) {
382383
response.getHeaders().setContentType(mediaType);
383384
}
385+
model = new TreeMap<>(model);
386+
String value = this.name + ": " + model.toString();
384387
ByteBuffer byteBuffer = ByteBuffer.wrap(value.getBytes(UTF_8));
385388
DataBuffer dataBuffer = new DefaultDataBufferFactory().wrap(byteBuffer);
386389
return response.writeWith(Flux.just(dataBuffer));

0 commit comments

Comments
 (0)