Skip to content

Commit 210e10c

Browse files
committed
Add AsyncHandlerMethodReturnValueHandler
Before this change HandlerMethodReturnValueHandler's were invoked in a specific order (type-based, annotation-based, custom). However handlers that deal with asynchronous return value handling need to always be considered first. This affects custom handlers in particular since they are normally ordered last. This change introduces an AsyncHandlerMethodReturnValueHandler sub-interface with a single method to determine if the return value is asynchronous and if it is to look for a matching handler only among those that are of type AsyncHandlerMethodReturnValueHandler. Issue: SPR-13083
1 parent 8187833 commit 210e10c

File tree

10 files changed

+203
-109
lines changed

10 files changed

+203
-109
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2002-2015 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.method.support;
17+
18+
import org.springframework.core.MethodParameter;
19+
20+
/**
21+
* A {@link HandlerMethodReturnValueHandler} that handles return values that
22+
* represent asynchronous computation. Such handlers need to be invoked with
23+
* precedence over other handlers that might otherwise match the return value
24+
* type -- e.g. a method that returns a Promise type that is also annotated with
25+
* {@code @ResponseBody}.
26+
*
27+
* <p>In {@link #handleReturnValue}, implementations of this class should create
28+
* a {@link org.springframework.web.context.request.async.DeferredResult} or
29+
* adapt to it and then invoke {@code WebAsyncManager} to start async processing.
30+
* For example:
31+
* <pre>
32+
* DeferredResult<?> deferredResult = (DeferredResult<?>) returnValue;
33+
* WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer);
34+
* </pre>
35+
* the return value to a DeferredResult
36+
*
37+
* @author Rossen Stoyanchev
38+
* @since 4.2
39+
*/
40+
public interface AsyncHandlerMethodReturnValueHandler extends HandlerMethodReturnValueHandler {
41+
42+
/**
43+
* Whether the given return value represents asynchronous computation.
44+
* @param returnValue the return value
45+
* @param returnType the return type
46+
* @return {@code true} if the return value is asynchronous.
47+
*/
48+
boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType);
49+
50+
}

spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite.java

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
* @author Rossen Stoyanchev
3535
* @since 3.1
3636
*/
37-
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
37+
public class HandlerMethodReturnValueHandlerComposite implements AsyncHandlerMethodReturnValueHandler {
3838

3939
protected final Log logger = LogFactory.getLog(getClass());
4040

@@ -58,6 +58,15 @@ public boolean supportsReturnType(MethodParameter returnType) {
5858
return getReturnValueHandler(returnType) != null;
5959
}
6060

61+
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
62+
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
63+
if (handler.supportsReturnType(returnType)) {
64+
return handler;
65+
}
66+
}
67+
return null;
68+
}
69+
6170
/**
6271
* Iterate over registered {@link HandlerMethodReturnValueHandler}s and invoke the one that supports it.
6372
* @throws IllegalStateException if no suitable {@link HandlerMethodReturnValueHandler} is found.
@@ -66,32 +75,41 @@ public boolean supportsReturnType(MethodParameter returnType) {
6675
public void handleReturnValue(Object returnValue, MethodParameter returnType,
6776
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
6877

69-
HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType);
78+
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
7079
Assert.notNull(handler, "Unknown return value type [" + returnType.getParameterType().getName() + "]");
7180
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
7281
}
7382

74-
/**
75-
* Find a registered {@link HandlerMethodReturnValueHandler} that supports the given return type.
76-
*/
77-
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
78-
for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) {
79-
if (logger.isTraceEnabled()) {
80-
logger.trace("Testing if return value handler [" + returnValueHandler + "] supports [" +
81-
returnType.getGenericParameterType() + "]");
83+
private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) {
84+
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
85+
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
86+
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
87+
continue;
8288
}
83-
if (returnValueHandler.supportsReturnType(returnType)) {
84-
return returnValueHandler;
89+
if (handler.supportsReturnType(returnType)) {
90+
return handler;
8591
}
8692
}
8793
return null;
8894
}
8995

96+
@Override
97+
public boolean isAsyncReturnValue(Object value, MethodParameter returnType) {
98+
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
99+
if (handler instanceof AsyncHandlerMethodReturnValueHandler) {
100+
if (((AsyncHandlerMethodReturnValueHandler) handler).isAsyncReturnValue(value, returnType)) {
101+
return true;
102+
}
103+
}
104+
}
105+
return false;
106+
}
107+
90108
/**
91109
* Add the given {@link HandlerMethodReturnValueHandler}.
92110
*/
93111
public HandlerMethodReturnValueHandlerComposite addHandler(HandlerMethodReturnValueHandler handler) {
94-
returnValueHandlers.add(handler);
112+
this.returnValueHandlers.add(handler);
95113
return this;
96114
}
97115

Lines changed: 67 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -22,76 +22,114 @@
2222
import org.springframework.core.MethodParameter;
2323

2424
import static org.junit.Assert.*;
25+
import static org.mockito.Mockito.*;
26+
27+
import java.lang.annotation.Documented;
28+
import java.lang.annotation.ElementType;
29+
import java.lang.annotation.Retention;
30+
import java.lang.annotation.RetentionPolicy;
31+
import java.lang.annotation.Target;
2532

2633
/**
2734
* Test fixture with {@link HandlerMethodReturnValueHandlerComposite}.
28-
*
2935
* @author Rossen Stoyanchev
3036
*/
37+
@SuppressWarnings("unused")
3138
public class HandlerMethodReturnValueHandlerCompositeTests {
3239

3340
private HandlerMethodReturnValueHandlerComposite handlers;
3441

42+
private HandlerMethodReturnValueHandler integerHandler;
43+
3544
ModelAndViewContainer mavContainer;
3645

37-
private MethodParameter paramInt;
46+
private MethodParameter integerType;
47+
48+
private MethodParameter stringType;
3849

39-
private MethodParameter paramStr;
4050

4151
@Before
4252
public void setUp() throws Exception {
43-
handlers = new HandlerMethodReturnValueHandlerComposite();
53+
54+
this.integerType = new MethodParameter(getClass().getDeclaredMethod("handleInteger"), -1);
55+
this.stringType = new MethodParameter(getClass().getDeclaredMethod("handleString"), -1);
56+
57+
this.integerHandler = mock(HandlerMethodReturnValueHandler.class);
58+
when(this.integerHandler.supportsReturnType(this.integerType)).thenReturn(true);
59+
60+
this.handlers = new HandlerMethodReturnValueHandlerComposite();
61+
this.handlers.addHandler(this.integerHandler);
62+
4463
mavContainer = new ModelAndViewContainer();
45-
paramInt = new MethodParameter(getClass().getDeclaredMethod("handleInteger"), -1);
46-
paramStr = new MethodParameter(getClass().getDeclaredMethod("handleString"), -1);
4764
}
4865

4966
@Test
5067
public void supportsReturnType() throws Exception {
51-
registerHandler(Integer.class);
52-
53-
assertTrue(this.handlers.supportsReturnType(paramInt));
54-
assertFalse(this.handlers.supportsReturnType(paramStr));
68+
assertTrue(this.handlers.supportsReturnType(this.integerType));
69+
assertFalse(this.handlers.supportsReturnType(this.stringType));
5570
}
5671

5772
@Test
5873
public void handleReturnValue() throws Exception {
59-
StubReturnValueHandler handler = registerHandler(Integer.class);
60-
this.handlers.handleReturnValue(Integer.valueOf(55), paramInt, mavContainer, null);
74+
this.handlers.handleReturnValue(55, this.integerType, this.mavContainer, null);
75+
verify(this.integerHandler).handleReturnValue(55, this.integerType, this.mavContainer, null);
76+
}
77+
78+
@Test
79+
public void handleReturnValueWithMultipleHandlers() throws Exception {
80+
HandlerMethodReturnValueHandler anotherIntegerHandler = mock(HandlerMethodReturnValueHandler.class);
81+
when(anotherIntegerHandler.supportsReturnType(this.integerType)).thenReturn(true);
6182

62-
assertEquals(Integer.valueOf(55), handler.getReturnValue());
83+
this.handlers.handleReturnValue(55, this.integerType, this.mavContainer, null);
84+
85+
verify(this.integerHandler).handleReturnValue(55, this.integerType, this.mavContainer, null);
86+
verifyNoMoreInteractions(anotherIntegerHandler);
6387
}
6488

89+
// SPR-13083
90+
6591
@Test
66-
public void handleReturnValueMultipleHandlers() throws Exception {
67-
StubReturnValueHandler h1 = registerHandler(Integer.class);
68-
StubReturnValueHandler h2 = registerHandler(Integer.class);
69-
this.handlers.handleReturnValue(Integer.valueOf(55), paramInt, mavContainer, null);
92+
public void handleReturnValueWithAsyncHandler() throws Exception {
93+
94+
Promise<Integer> promise = new Promise<>();
95+
MethodParameter promiseType = new MethodParameter(getClass().getDeclaredMethod("handlePromise"), -1);
96+
97+
HandlerMethodReturnValueHandler responseBodyHandler = mock(HandlerMethodReturnValueHandler.class);
98+
when(responseBodyHandler.supportsReturnType(promiseType)).thenReturn(true);
99+
this.handlers.addHandler(responseBodyHandler);
100+
101+
AsyncHandlerMethodReturnValueHandler promiseHandler = mock(AsyncHandlerMethodReturnValueHandler.class);
102+
when(promiseHandler.supportsReturnType(promiseType)).thenReturn(true);
103+
when(promiseHandler.isAsyncReturnValue(promise, promiseType)).thenReturn(true);
104+
this.handlers.addHandler(promiseHandler);
105+
106+
this.handlers.handleReturnValue(promise, promiseType, this.mavContainer, null);
70107

71-
assertEquals("Didn't use the 1st registered handler", Integer.valueOf(55), h1.getReturnValue());
72-
assertNull("Shouldn't have use the 2nd registered handler", h2.getReturnValue());
108+
verify(promiseHandler).isAsyncReturnValue(promise, promiseType);
109+
verify(promiseHandler).supportsReturnType(promiseType);
110+
verify(promiseHandler).handleReturnValue(promise, promiseType, this.mavContainer, null);
111+
verifyNoMoreInteractions(promiseHandler);
112+
verifyNoMoreInteractions(responseBodyHandler);
73113
}
74114

75115
@Test(expected=IllegalArgumentException.class)
76116
public void noSuitableReturnValueHandler() throws Exception {
77-
registerHandler(Integer.class);
78-
this.handlers.handleReturnValue("value", paramStr, null, null);
117+
this.handlers.handleReturnValue("value", this.stringType, null, null);
79118
}
80119

81-
private StubReturnValueHandler registerHandler(Class<?> returnType) {
82-
StubReturnValueHandler handler = new StubReturnValueHandler(returnType);
83-
handlers.addHandler(handler);
84-
return handler;
85-
}
86120

87-
@SuppressWarnings("unused")
88121
private Integer handleInteger() {
89122
return null;
90123
}
91124

92-
@SuppressWarnings("unused")
93125
private String handleString() {
94126
return null;
95127
}
96128

129+
private Promise<Integer> handlePromise() {
130+
return null;
131+
}
132+
133+
private static class Promise<T> {}
134+
97135
}

spring-web/src/test/java/org/springframework/web/method/support/StubReturnValueHandler.java

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

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AsyncTaskMethodReturnValueHandler.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -21,7 +21,7 @@
2121
import org.springframework.web.context.request.NativeWebRequest;
2222
import org.springframework.web.context.request.async.WebAsyncTask;
2323
import org.springframework.web.context.request.async.WebAsyncUtils;
24-
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
24+
import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandler;
2525
import org.springframework.web.method.support.ModelAndViewContainer;
2626

2727
/**
@@ -30,7 +30,7 @@
3030
* @author Rossen Stoyanchev
3131
* @since 3.2
3232
*/
33-
public class AsyncTaskMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
33+
public class AsyncTaskMethodReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
3434

3535
private final BeanFactory beanFactory;
3636

@@ -45,6 +45,11 @@ public boolean supportsReturnType(MethodParameter returnType) {
4545
return WebAsyncTask.class.isAssignableFrom(returnType.getParameterType());
4646
}
4747

48+
@Override
49+
public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {
50+
return (returnValue != null && returnValue instanceof WebAsyncTask);
51+
}
52+
4853
@Override
4954
public void handleReturnValue(Object returnValue, MethodParameter returnType,
5055
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

0 commit comments

Comments
 (0)