Skip to content

Commit ae1ed16

Browse files
committed
Async return values refactoring in Spring MVC
Revise Javadoc on AsyncHandlerMethodReturnValueHandler to clarify its main purpose is to prioritze custom async return value handlers ahead of built-in ones. Also replace the interface from built-in handlers which are prioritized already. Remove DeferredResultAdapter and ResponseBodyEmitterAdapter -- introduced in 4.3 for custom async return value handling, since for 5.0 we will add built-in support for reactive types and the value of these contracts becomes very marginal. Issue: SPR-15365
1 parent cfc89eb commit ae1ed16

File tree

10 files changed

+136
-384
lines changed

10 files changed

+136
-384
lines changed

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

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2017 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.
@@ -19,20 +19,16 @@
1919
import org.springframework.core.MethodParameter;
2020

2121
/**
22-
* A {@link HandlerMethodReturnValueHandler} that handles return values that
23-
* represent asynchronous computation. Such handlers need to be invoked with
24-
* precedence over other handlers that might otherwise match the return value
25-
* type: e.g. a method that returns a Promise type that is also annotated with
26-
* {@code @ResponseBody}.
22+
* A return value handler that supports async types. Such return value types
23+
* need to be handled with priority so the async value can be "unwrapped".
2724
*
28-
* <p>In {@link #handleReturnValue}, implementations of this class should create
29-
* a {@link org.springframework.web.context.request.async.DeferredResult} or
30-
* adapt to it and then invoke {@code WebAsyncManager} to start async processing.
31-
* For example:
32-
* <pre>
33-
* DeferredResult<?> deferredResult = (DeferredResult<?>) returnValue;
34-
* WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer);
35-
* </pre>
25+
* <p><strong>Note: </strong> implementing this contract is not required but it
26+
* should be implemented when the handler needs to be prioritized ahead of others.
27+
* For example custom (async) handlers, by default ordered after built-in
28+
* handlers, should take precedence over {@code @ResponseBody} or
29+
* {@code @ModelAttribute} handling, which should occur once the async value is
30+
* ready. By contrast, built-in (async) handlers are already ordered ahead of
31+
* sync handlers.
3632
*
3733
* @author Rossen Stoyanchev
3834
* @since 4.2

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
* @author Rossen Stoyanchev
3434
* @since 3.1
3535
*/
36-
public class HandlerMethodReturnValueHandlerComposite implements AsyncHandlerMethodReturnValueHandler {
36+
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
3737

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

@@ -94,8 +94,7 @@ private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParame
9494
return null;
9595
}
9696

97-
@Override
98-
public boolean isAsyncReturnValue(Object value, MethodParameter returnType) {
97+
private boolean isAsyncReturnValue(Object value, MethodParameter returnType) {
9998
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
10099
if (handler instanceof AsyncHandlerMethodReturnValueHandler) {
101100
if (((AsyncHandlerMethodReturnValueHandler) handler).isAsyncReturnValue(value, returnType)) {

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

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2017 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,14 +21,12 @@
2121

2222
import org.springframework.core.MethodParameter;
2323

24-
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;
24+
import static org.junit.Assert.assertFalse;
25+
import static org.junit.Assert.assertTrue;
26+
import static org.mockito.Mockito.mock;
27+
import static org.mockito.Mockito.verify;
28+
import static org.mockito.Mockito.verifyNoMoreInteractions;
29+
import static org.mockito.Mockito.when;
3230

3331
/**
3432
* Test fixture with {@link HandlerMethodReturnValueHandlerComposite}.
@@ -86,9 +84,7 @@ public void handleReturnValueWithMultipleHandlers() throws Exception {
8684
verifyNoMoreInteractions(anotherIntegerHandler);
8785
}
8886

89-
// SPR-13083
90-
91-
@Test
87+
@Test // SPR-13083
9288
public void handleReturnValueWithAsyncHandler() throws Exception {
9389

9490
Promise<Integer> promise = new Promise<>();

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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.AsyncHandlerMethodReturnValueHandler;
24+
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
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 AsyncHandlerMethodReturnValueHandler {
33+
public class AsyncTaskMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
3434

3535
private final BeanFactory beanFactory;
3636

@@ -45,11 +45,6 @@ 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-
5348
@Override
5449
public void handleReturnValue(Object returnValue, MethodParameter returnType,
5550
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

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

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

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

3535
@Override
3636
public boolean supportsReturnType(MethodParameter returnType) {
3737
return Callable.class.isAssignableFrom(returnType.getParameterType());
3838
}
3939

40-
@Override
41-
public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {
42-
return (returnValue != null && returnValue instanceof Callable);
43-
}
44-
4540
@Override
4641
public void handleReturnValue(Object returnValue, MethodParameter returnType,
4742
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

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

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

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

Lines changed: 48 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -16,70 +16,34 @@
1616

1717
package org.springframework.web.servlet.mvc.method.annotation;
1818

19-
import java.util.HashMap;
20-
import java.util.Map;
2119
import java.util.concurrent.CompletionStage;
2220
import java.util.function.BiFunction;
2321

2422
import org.springframework.core.MethodParameter;
25-
import org.springframework.util.Assert;
2623
import org.springframework.util.concurrent.ListenableFuture;
2724
import org.springframework.util.concurrent.ListenableFutureCallback;
2825
import org.springframework.web.context.request.NativeWebRequest;
2926
import org.springframework.web.context.request.async.DeferredResult;
3027
import org.springframework.web.context.request.async.WebAsyncUtils;
31-
import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandler;
28+
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
3229
import org.springframework.web.method.support.ModelAndViewContainer;
3330

3431
/**
35-
* Handler for return values of type {@link DeferredResult}, {@link ListenableFuture},
36-
* {@link CompletionStage} and any other async type with a {@link #getAdapterMap()
37-
* registered adapter}.
32+
* Handler for return values of type {@link DeferredResult},
33+
* {@link ListenableFuture}, and {@link CompletionStage}.
3834
*
3935
* @author Rossen Stoyanchev
4036
* @since 3.2
4137
*/
42-
public class DeferredResultMethodReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
43-
44-
private final Map<Class<?>, DeferredResultAdapter> adapterMap;
45-
46-
47-
public DeferredResultMethodReturnValueHandler() {
48-
this.adapterMap = new HashMap<>(5);
49-
this.adapterMap.put(DeferredResult.class, new SimpleDeferredResultAdapter());
50-
this.adapterMap.put(ListenableFuture.class, new ListenableFutureAdapter());
51-
this.adapterMap.put(CompletionStage.class, new CompletionStageAdapter());
52-
}
53-
54-
55-
/**
56-
* Return the map with {@code DeferredResult} adapters.
57-
* <p>By default the map contains adapters for {@code DeferredResult}, which
58-
* simply downcasts, {@link ListenableFuture}, and {@link CompletionStage}.
59-
* @return the map of adapters
60-
*/
61-
public Map<Class<?>, DeferredResultAdapter> getAdapterMap() {
62-
return this.adapterMap;
63-
}
64-
65-
private DeferredResultAdapter getAdapterFor(Class<?> type) {
66-
for (Class<?> adapteeType : getAdapterMap().keySet()) {
67-
if (adapteeType.isAssignableFrom(type)) {
68-
return getAdapterMap().get(adapteeType);
69-
}
70-
}
71-
return null;
72-
}
38+
public class DeferredResultMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
7339

7440

7541
@Override
7642
public boolean supportsReturnType(MethodParameter returnType) {
77-
return (getAdapterFor(returnType.getParameterType()) != null);
78-
}
79-
80-
@Override
81-
public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {
82-
return (returnValue != null && (getAdapterFor(returnValue.getClass()) != null));
43+
Class<?> type = returnType.getParameterType();
44+
return DeferredResult.class.isAssignableFrom(type) ||
45+
ListenableFuture.class.isAssignableFrom(type) ||
46+
CompletionStage.class.isAssignableFrom(type);
8347
}
8448

8549
@Override
@@ -91,78 +55,52 @@ public void handleReturnValue(Object returnValue, MethodParameter returnType,
9155
return;
9256
}
9357

94-
DeferredResultAdapter adapter = getAdapterFor(returnValue.getClass());
95-
if (adapter == null) {
96-
throw new IllegalStateException(
97-
"Could not find DeferredResultAdapter for return value type: " + returnValue.getClass());
98-
}
99-
DeferredResult<?> result = adapter.adaptToDeferredResult(returnValue);
100-
WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(result, mavContainer);
101-
}
102-
103-
104-
/**
105-
* Adapter for {@code DeferredResult} return values.
106-
*/
107-
private static class SimpleDeferredResultAdapter implements DeferredResultAdapter {
58+
DeferredResult<?> result;
10859

109-
@Override
110-
public DeferredResult<?> adaptToDeferredResult(Object returnValue) {
111-
Assert.isInstanceOf(DeferredResult.class, returnValue, "DeferredResult expected");
112-
return (DeferredResult<?>) returnValue;
60+
if (returnValue instanceof DeferredResult) {
61+
result = (DeferredResult<?>) returnValue;
11362
}
114-
}
115-
116-
117-
/**
118-
* Adapter for {@code ListenableFuture} return values.
119-
*/
120-
private static class ListenableFutureAdapter implements DeferredResultAdapter {
121-
122-
@Override
123-
public DeferredResult<?> adaptToDeferredResult(Object returnValue) {
124-
Assert.isInstanceOf(ListenableFuture.class, returnValue, "ListenableFuture expected");
125-
final DeferredResult<Object> result = new DeferredResult<>();
126-
((ListenableFuture<?>) returnValue).addCallback(new ListenableFutureCallback<Object>() {
127-
@Override
128-
public void onSuccess(Object value) {
129-
result.setResult(value);
130-
}
131-
@Override
132-
public void onFailure(Throwable ex) {
133-
result.setErrorResult(ex);
134-
}
135-
});
136-
return result;
63+
else if (returnValue instanceof ListenableFuture) {
64+
result = adaptListenableFuture((ListenableFuture<?>) returnValue);
65+
}
66+
else if (returnValue instanceof CompletionStage) {
67+
result = adaptCompletionStage((CompletionStage<?>) returnValue);
68+
}
69+
else {
70+
// Should not happen...
71+
throw new IllegalStateException("Unexpected return value type: " + returnValue);
13772
}
138-
}
13973

74+
WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(result, mavContainer);
75+
}
14076

141-
/**
142-
* Adapter for {@code CompletionStage} return values.
143-
*/
144-
private static class CompletionStageAdapter implements DeferredResultAdapter {
77+
private DeferredResult<Object> adaptListenableFuture(ListenableFuture<?> future) {
78+
DeferredResult<Object> result = new DeferredResult<>();
79+
future.addCallback(new ListenableFutureCallback<Object>() {
80+
@Override
81+
public void onSuccess(Object value) {
82+
result.setResult(value);
83+
}
84+
@Override
85+
public void onFailure(Throwable ex) {
86+
result.setErrorResult(ex);
87+
}
88+
});
89+
return result;
90+
}
14591

146-
@Override
147-
public DeferredResult<?> adaptToDeferredResult(Object returnValue) {
148-
Assert.isInstanceOf(CompletionStage.class, returnValue, "CompletionStage expected");
149-
final DeferredResult<Object> result = new DeferredResult<>();
150-
@SuppressWarnings("unchecked")
151-
CompletionStage<?> future = (CompletionStage<?>) returnValue;
152-
future.handle(new BiFunction<Object, Throwable, Object>() {
153-
@Override
154-
public Object apply(Object value, Throwable ex) {
155-
if (ex != null) {
156-
result.setErrorResult(ex);
157-
}
158-
else {
159-
result.setResult(value);
160-
}
161-
return null;
162-
}
163-
});
164-
return result;
165-
}
92+
private DeferredResult<Object> adaptCompletionStage(CompletionStage<?> future) {
93+
DeferredResult<Object> result = new DeferredResult<>();
94+
future.handle((BiFunction<Object, Throwable, Object>) (value, ex) -> {
95+
if (ex != null) {
96+
result.setErrorResult(ex);
97+
}
98+
else {
99+
result.setResult(value);
100+
}
101+
return null;
102+
});
103+
return result;
166104
}
167105

168106
}

0 commit comments

Comments
 (0)