Skip to content

Commit 971ccab

Browse files
committed
DeferredResult/ResponseBodyEmitter adapter mechanism
The DeferredResult~ and the ResponseBodyEmitterReturnValueHandler now each expose an adapter mechanism for plugging in other async return value types. As a result the ListenableFutureReturnValueHandler and CompletionStageReturnValueHandler are no longer needed and are now deprecated. Issue: SPR-14046
1 parent 2152f43 commit 971ccab

File tree

9 files changed

+479
-59
lines changed

9 files changed

+479
-59
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@
3434
*
3535
* @author Sebastien Deleuze
3636
* @since 4.2
37+
*
38+
* @deprecated as of 4.3 {@link DeferredResultMethodReturnValueHandler} supports
39+
* CompletionStage return values via an adapter mechanism.
3740
*/
41+
@Deprecated
3842
@UsesJava8
3943
public class CompletionStageReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
4044

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.servlet.mvc.method.annotation;
17+
18+
import org.springframework.web.context.request.async.DeferredResult;
19+
20+
/**
21+
* Contract to adapt a single-value async return value to {@code DeferredResult}.
22+
*
23+
* @author Rossen Stoyanchev
24+
* @since 4.3
25+
*/
26+
public interface DeferredResultAdapter {
27+
28+
/**
29+
* Create a {@code DeferredResult} for the given return value.
30+
* @param returnValue the return value, never {@code null}
31+
* @return the DeferredResult
32+
*/
33+
DeferredResult<?> adaptToDeferredResult(Object returnValue);
34+
35+
}

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

Lines changed: 117 additions & 6 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-2016 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.
@@ -16,29 +16,74 @@
1616

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

19+
import java.util.HashMap;
20+
import java.util.Map;
21+
import java.util.concurrent.CompletionStage;
22+
import java.util.function.BiFunction;
23+
1924
import org.springframework.core.MethodParameter;
25+
import org.springframework.lang.UsesJava8;
26+
import org.springframework.util.Assert;
27+
import org.springframework.util.ClassUtils;
28+
import org.springframework.util.concurrent.ListenableFuture;
29+
import org.springframework.util.concurrent.ListenableFutureCallback;
2030
import org.springframework.web.context.request.NativeWebRequest;
2131
import org.springframework.web.context.request.async.DeferredResult;
2232
import org.springframework.web.context.request.async.WebAsyncUtils;
2333
import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandler;
2434
import org.springframework.web.method.support.ModelAndViewContainer;
2535

2636
/**
27-
* Handles return values of type {@link DeferredResult}.
37+
* Handler for return values of type {@link DeferredResult}, {@link ListenableFuture},
38+
* {@link CompletionStage} and any other async type with a {@link #getAdapterMap()
39+
* registered adapter}.
2840
*
2941
* @author Rossen Stoyanchev
3042
* @since 3.2
3143
*/
3244
public class DeferredResultMethodReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
3345

46+
private final Map<Class<?>, DeferredResultAdapter> adapterMap;
47+
48+
49+
public DeferredResultMethodReturnValueHandler() {
50+
this.adapterMap = new HashMap<Class<?>, DeferredResultAdapter>(5);
51+
this.adapterMap.put(DeferredResult.class, new SimpleDeferredResultAdapter());
52+
this.adapterMap.put(ListenableFuture.class, new ListenableFutureAdapter());
53+
if (ClassUtils.isPresent("java.util.concurrent.CompletionStage", getClass().getClassLoader())) {
54+
this.adapterMap.put(CompletionStage.class, new CompletionStageAdapter());
55+
}
56+
}
57+
58+
59+
/**
60+
* Return the map with {@code DeferredResult} adapters.
61+
* <p>By default the map contains adapters for {@code DeferredResult}, which
62+
* simply downcasts, {@link ListenableFuture}, and {@link CompletionStage}.
63+
* @return the map of adapters
64+
*/
65+
public Map<Class<?>, DeferredResultAdapter> getAdapterMap() {
66+
return this.adapterMap;
67+
}
68+
69+
private DeferredResultAdapter getAdapterFor(Class<?> type) {
70+
for (Class<?> adapteeType : getAdapterMap().keySet()) {
71+
if (adapteeType.isAssignableFrom(type)) {
72+
return getAdapterMap().get(adapteeType);
73+
}
74+
}
75+
return null;
76+
}
77+
78+
3479
@Override
3580
public boolean supportsReturnType(MethodParameter returnType) {
36-
return DeferredResult.class.isAssignableFrom(returnType.getParameterType());
81+
return (getAdapterFor(returnType.getParameterType()) != null);
3782
}
3883

3984
@Override
4085
public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {
41-
return (returnValue != null && returnValue instanceof DeferredResult);
86+
return (returnValue != null && (getAdapterFor(returnValue.getClass()) != null));
4287
}
4388

4489
@Override
@@ -50,8 +95,74 @@ public void handleReturnValue(Object returnValue, MethodParameter returnType,
5095
return;
5196
}
5297

53-
DeferredResult<?> deferredResult = (DeferredResult<?>) returnValue;
54-
WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer);
98+
DeferredResultAdapter adapter = getAdapterFor(returnValue.getClass());
99+
Assert.notNull(adapter);
100+
DeferredResult<?> result = adapter.adaptToDeferredResult(returnValue);
101+
WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(result, mavContainer);
102+
}
103+
104+
105+
/**
106+
* Adapter for {@code DeferredResult} return values.
107+
*/
108+
private static class SimpleDeferredResultAdapter implements DeferredResultAdapter {
109+
110+
@Override
111+
public DeferredResult<?> adaptToDeferredResult(Object returnValue) {
112+
Assert.isInstanceOf(DeferredResult.class, returnValue);
113+
return (DeferredResult<?>) returnValue;
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);
125+
final DeferredResult<Object> result = new DeferredResult<Object>();
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;
137+
}
138+
}
139+
140+
/**
141+
* Adapter for {@code CompletionStage} return values.
142+
*/
143+
@UsesJava8
144+
private static class CompletionStageAdapter implements DeferredResultAdapter {
145+
146+
@Override
147+
public DeferredResult<?> adaptToDeferredResult(Object returnValue) {
148+
Assert.isInstanceOf(CompletionStage.class, returnValue);
149+
final DeferredResult<Object> result = new DeferredResult<Object>();
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+
}
55166
}
56167

57168
}

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

Lines changed: 5 additions & 1 deletion
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-2016 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.
@@ -31,7 +31,11 @@
3131
*
3232
* @author Rossen Stoyanchev
3333
* @since 4.1
34+
*
35+
* @deprecated as of 4.3 {@link DeferredResultMethodReturnValueHandler} supports
36+
* ListenableFuture return values via an adapter mechanism.
3437
*/
38+
@Deprecated
3539
public class ListenableFutureReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
3640

3741
@Override

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
4949
import org.springframework.ui.ModelMap;
5050
import org.springframework.util.Assert;
51-
import org.springframework.util.ClassUtils;
5251
import org.springframework.util.CollectionUtils;
5352
import org.springframework.util.ReflectionUtils.MethodFilter;
5453
import org.springframework.web.accept.ContentNegotiationManager;
@@ -117,10 +116,6 @@
117116
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
118117
implements BeanFactoryAware, InitializingBean {
119118

120-
private static final boolean completionStagePresent = ClassUtils.isPresent(
121-
"java.util.concurrent.CompletionStage", RequestMappingHandlerAdapter.class.getClassLoader());
122-
123-
124119
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
125120

126121
private HandlerMethodArgumentResolverComposite argumentResolvers;
@@ -677,10 +672,6 @@ private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
677672
handlers.add(new CallableMethodReturnValueHandler());
678673
handlers.add(new DeferredResultMethodReturnValueHandler());
679674
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
680-
handlers.add(new ListenableFutureReturnValueHandler());
681-
if (completionStagePresent) {
682-
handlers.add(new CompletionStageReturnValueHandler());
683-
}
684675

685676
// Annotation-based return value types
686677
handlers.add(new ModelAttributeMethodProcessor(false));
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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.servlet.mvc.method.annotation;
17+
18+
import org.springframework.http.server.ServerHttpResponse;
19+
20+
21+
/**
22+
* Contract to adapt streaming async types to {@code ResponseBodyEmitter}.
23+
* @author Rossen Stoyanchev
24+
* @since 4.3
25+
*/
26+
public interface ResponseBodyEmitterAdapter {
27+
28+
/**
29+
* Obtain a {@code ResponseBodyEmitter} for the given return value. If
30+
* the return is the body {@code ResponseEntity} then the given
31+
* {@code ServerHttpResponse} contains its status and headers.
32+
* @param returnValue the return value, never {@code null}
33+
* @param response the response
34+
* @return the return value adapted to a {@code ResponseBodyEmitter}
35+
*/
36+
ResponseBodyEmitter adaptToEmitter(Object returnValue, ServerHttpResponse response);
37+
38+
}

0 commit comments

Comments
 (0)