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
This commit is contained in:
@@ -34,7 +34,11 @@ import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 4.2
|
||||
*
|
||||
* @deprecated as of 4.3 {@link DeferredResultMethodReturnValueHandler} supports
|
||||
* CompletionStage return values via an adapter mechanism.
|
||||
*/
|
||||
@Deprecated
|
||||
@UsesJava8
|
||||
public class CompletionStageReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import org.springframework.web.context.request.async.DeferredResult;
|
||||
|
||||
/**
|
||||
* Contract to adapt a single-value async return value to {@code DeferredResult}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.3
|
||||
*/
|
||||
public interface DeferredResultAdapter {
|
||||
|
||||
/**
|
||||
* Create a {@code DeferredResult} for the given return value.
|
||||
* @param returnValue the return value, never {@code null}
|
||||
* @return the DeferredResult
|
||||
*/
|
||||
DeferredResult<?> adaptToDeferredResult(Object returnValue);
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,7 +16,17 @@
|
||||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.lang.UsesJava8;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.ListenableFutureCallback;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.async.DeferredResult;
|
||||
import org.springframework.web.context.request.async.WebAsyncUtils;
|
||||
@@ -24,21 +34,56 @@ import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandl
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* Handles return values of type {@link DeferredResult}.
|
||||
* Handler for return values of type {@link DeferredResult}, {@link ListenableFuture},
|
||||
* {@link CompletionStage} and any other async type with a {@link #getAdapterMap()
|
||||
* registered adapter}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public class DeferredResultMethodReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
|
||||
|
||||
private final Map<Class<?>, DeferredResultAdapter> adapterMap;
|
||||
|
||||
|
||||
public DeferredResultMethodReturnValueHandler() {
|
||||
this.adapterMap = new HashMap<Class<?>, DeferredResultAdapter>(5);
|
||||
this.adapterMap.put(DeferredResult.class, new SimpleDeferredResultAdapter());
|
||||
this.adapterMap.put(ListenableFuture.class, new ListenableFutureAdapter());
|
||||
if (ClassUtils.isPresent("java.util.concurrent.CompletionStage", getClass().getClassLoader())) {
|
||||
this.adapterMap.put(CompletionStage.class, new CompletionStageAdapter());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the map with {@code DeferredResult} adapters.
|
||||
* <p>By default the map contains adapters for {@code DeferredResult}, which
|
||||
* simply downcasts, {@link ListenableFuture}, and {@link CompletionStage}.
|
||||
* @return the map of adapters
|
||||
*/
|
||||
public Map<Class<?>, DeferredResultAdapter> getAdapterMap() {
|
||||
return this.adapterMap;
|
||||
}
|
||||
|
||||
private DeferredResultAdapter getAdapterFor(Class<?> type) {
|
||||
for (Class<?> adapteeType : getAdapterMap().keySet()) {
|
||||
if (adapteeType.isAssignableFrom(type)) {
|
||||
return getAdapterMap().get(adapteeType);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
return DeferredResult.class.isAssignableFrom(returnType.getParameterType());
|
||||
return (getAdapterFor(returnType.getParameterType()) != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {
|
||||
return (returnValue != null && returnValue instanceof DeferredResult);
|
||||
return (returnValue != null && (getAdapterFor(returnValue.getClass()) != null));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -50,8 +95,74 @@ public class DeferredResultMethodReturnValueHandler implements AsyncHandlerMetho
|
||||
return;
|
||||
}
|
||||
|
||||
DeferredResult<?> deferredResult = (DeferredResult<?>) returnValue;
|
||||
WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer);
|
||||
DeferredResultAdapter adapter = getAdapterFor(returnValue.getClass());
|
||||
Assert.notNull(adapter);
|
||||
DeferredResult<?> result = adapter.adaptToDeferredResult(returnValue);
|
||||
WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(result, mavContainer);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adapter for {@code DeferredResult} return values.
|
||||
*/
|
||||
private static class SimpleDeferredResultAdapter implements DeferredResultAdapter {
|
||||
|
||||
@Override
|
||||
public DeferredResult<?> adaptToDeferredResult(Object returnValue) {
|
||||
Assert.isInstanceOf(DeferredResult.class, returnValue);
|
||||
return (DeferredResult<?>) returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter for {@code ListenableFuture} return values.
|
||||
*/
|
||||
private static class ListenableFutureAdapter implements DeferredResultAdapter {
|
||||
|
||||
@Override
|
||||
public DeferredResult<?> adaptToDeferredResult(Object returnValue) {
|
||||
Assert.isInstanceOf(ListenableFuture.class, returnValue);
|
||||
final DeferredResult<Object> result = new DeferredResult<Object>();
|
||||
((ListenableFuture<?>) returnValue).addCallback(new ListenableFutureCallback<Object>() {
|
||||
@Override
|
||||
public void onSuccess(Object value) {
|
||||
result.setResult(value);
|
||||
}
|
||||
@Override
|
||||
public void onFailure(Throwable ex) {
|
||||
result.setErrorResult(ex);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter for {@code CompletionStage} return values.
|
||||
*/
|
||||
@UsesJava8
|
||||
private static class CompletionStageAdapter implements DeferredResultAdapter {
|
||||
|
||||
@Override
|
||||
public DeferredResult<?> adaptToDeferredResult(Object returnValue) {
|
||||
Assert.isInstanceOf(CompletionStage.class, returnValue);
|
||||
final DeferredResult<Object> result = new DeferredResult<Object>();
|
||||
@SuppressWarnings("unchecked")
|
||||
CompletionStage<?> future = (CompletionStage<?>) returnValue;
|
||||
future.handle(new BiFunction<Object, Throwable, Object>() {
|
||||
@Override
|
||||
public Object apply(Object value, Throwable ex) {
|
||||
if (ex != null) {
|
||||
result.setErrorResult(ex);
|
||||
}
|
||||
else {
|
||||
result.setResult(value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,7 +31,11 @@ import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.1
|
||||
*
|
||||
* @deprecated as of 4.3 {@link DeferredResultMethodReturnValueHandler} supports
|
||||
* ListenableFuture return values via an adapter mechanism.
|
||||
*/
|
||||
@Deprecated
|
||||
public class ListenableFutureReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -48,7 +48,6 @@ import org.springframework.http.converter.support.AllEncompassingFormHttpMessage
|
||||
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodFilter;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
@@ -117,10 +116,6 @@ import org.springframework.web.util.WebUtils;
|
||||
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
|
||||
implements BeanFactoryAware, InitializingBean {
|
||||
|
||||
private static final boolean completionStagePresent = ClassUtils.isPresent(
|
||||
"java.util.concurrent.CompletionStage", RequestMappingHandlerAdapter.class.getClassLoader());
|
||||
|
||||
|
||||
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
|
||||
|
||||
private HandlerMethodArgumentResolverComposite argumentResolvers;
|
||||
@@ -677,10 +672,6 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
|
||||
handlers.add(new CallableMethodReturnValueHandler());
|
||||
handlers.add(new DeferredResultMethodReturnValueHandler());
|
||||
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
|
||||
handlers.add(new ListenableFutureReturnValueHandler());
|
||||
if (completionStagePresent) {
|
||||
handlers.add(new CompletionStageReturnValueHandler());
|
||||
}
|
||||
|
||||
// Annotation-based return value types
|
||||
handlers.add(new ModelAttributeMethodProcessor(false));
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
|
||||
|
||||
/**
|
||||
* Contract to adapt streaming async types to {@code ResponseBodyEmitter}.
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.3
|
||||
*/
|
||||
public interface ResponseBodyEmitterAdapter {
|
||||
|
||||
/**
|
||||
* Obtain a {@code ResponseBodyEmitter} for the given return value. If
|
||||
* the return is the body {@code ResponseEntity} then the given
|
||||
* {@code ServerHttpResponse} contains its status and headers.
|
||||
* @param returnValue the return value, never {@code null}
|
||||
* @param response the response
|
||||
* @return the return value adapted to a {@code ResponseBodyEmitter}
|
||||
*/
|
||||
ResponseBodyEmitter adaptToEmitter(Object returnValue, ServerHttpResponse response);
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,8 +18,9 @@ package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import java.util.Map;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@@ -44,8 +45,9 @@ import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandl
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* Supports return values of type {@link ResponseBodyEmitter} and also
|
||||
* {@code ResponseEntity<ResponseBodyEmitter>}.
|
||||
* Handler for return values of type {@link ResponseBodyEmitter} (and the
|
||||
* {@code ResponseEntity<ResponseBodyEmitter>} sub-class) as well as any other
|
||||
* async type with a {@link #getAdapterMap() registered adapter}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.2
|
||||
@@ -54,36 +56,61 @@ public class ResponseBodyEmitterReturnValueHandler implements AsyncHandlerMethod
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ResponseBodyEmitterReturnValueHandler.class);
|
||||
|
||||
|
||||
private final List<HttpMessageConverter<?>> messageConverters;
|
||||
|
||||
private final Map<Class<?>, ResponseBodyEmitterAdapter> adapterMap;
|
||||
|
||||
|
||||
public ResponseBodyEmitterReturnValueHandler(List<HttpMessageConverter<?>> messageConverters) {
|
||||
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
|
||||
this.messageConverters = messageConverters;
|
||||
this.adapterMap = new HashMap<Class<?>, ResponseBodyEmitterAdapter>(3);
|
||||
this.adapterMap.put(ResponseBodyEmitter.class, new SimpleResponseBodyEmitterAdapter());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the map with {@code ResponseBodyEmitter} adapters.
|
||||
* By default the map contains a single adapter {@code ResponseBodyEmitter}
|
||||
* that simply downcasts the return value.
|
||||
* @return the map of adapters
|
||||
*/
|
||||
public Map<Class<?>, ResponseBodyEmitterAdapter> getAdapterMap() {
|
||||
return this.adapterMap;
|
||||
}
|
||||
|
||||
private ResponseBodyEmitterAdapter getAdapterFor(Class<?> type) {
|
||||
for (Class<?> adapteeType : getAdapterMap().keySet()) {
|
||||
if (adapteeType.isAssignableFrom(type)) {
|
||||
return getAdapterMap().get(adapteeType);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
if (ResponseBodyEmitter.class.isAssignableFrom(returnType.getParameterType())) {
|
||||
return true;
|
||||
Class<?> bodyType;
|
||||
if (ResponseEntity.class.isAssignableFrom(returnType.getParameterType())) {
|
||||
bodyType = ResolvableType.forMethodParameter(returnType).getGeneric(0).resolve();
|
||||
}
|
||||
else if (ResponseEntity.class.isAssignableFrom(returnType.getParameterType())) {
|
||||
Class<?> bodyType = ResolvableType.forMethodParameter(returnType).getGeneric(0).resolve();
|
||||
return (bodyType != null && ResponseBodyEmitter.class.isAssignableFrom(bodyType));
|
||||
else {
|
||||
bodyType = returnType.getParameterType();
|
||||
}
|
||||
return false;
|
||||
return (getAdapterFor(bodyType) != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {
|
||||
if (returnValue != null) {
|
||||
if (returnValue instanceof ResponseBodyEmitter) {
|
||||
return true;
|
||||
Object adaptFrom = returnValue;
|
||||
if (returnValue instanceof ResponseEntity) {
|
||||
adaptFrom = ((ResponseEntity) returnValue).getBody();
|
||||
}
|
||||
else if (returnValue instanceof ResponseEntity) {
|
||||
Object body = ((ResponseEntity) returnValue).getBody();
|
||||
return (body != null && body instanceof ResponseBodyEmitter);
|
||||
if (adaptFrom != null) {
|
||||
return (getAdapterFor(adaptFrom.getClass()) != null);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -115,8 +142,9 @@ public class ResponseBodyEmitterReturnValueHandler implements AsyncHandlerMethod
|
||||
ServletRequest request = webRequest.getNativeRequest(ServletRequest.class);
|
||||
ShallowEtagHeaderFilter.disableContentCaching(request);
|
||||
|
||||
Assert.isInstanceOf(ResponseBodyEmitter.class, returnValue);
|
||||
ResponseBodyEmitter emitter = (ResponseBodyEmitter) returnValue;
|
||||
ResponseBodyEmitterAdapter adapter = getAdapterFor(returnValue.getClass());
|
||||
Assert.notNull(adapter);
|
||||
ResponseBodyEmitter emitter = adapter.adaptToEmitter(returnValue, outputMessage);
|
||||
emitter.extendResponse(outputMessage);
|
||||
|
||||
// Commit the response and wrap to ignore further header changes
|
||||
@@ -132,6 +160,18 @@ public class ResponseBodyEmitterReturnValueHandler implements AsyncHandlerMethod
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adapter for {@code ResponseBodyEmitter} return values.
|
||||
*/
|
||||
private static class SimpleResponseBodyEmitterAdapter implements ResponseBodyEmitterAdapter {
|
||||
|
||||
@Override
|
||||
public ResponseBodyEmitter adaptToEmitter(Object returnValue, ServerHttpResponse response) {
|
||||
Assert.isInstanceOf(ResponseBodyEmitter.class, returnValue);
|
||||
return (ResponseBodyEmitter) returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ResponseBodyEmitter.Handler that writes with HttpMessageConverter's.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user