Properly support ResponseEntity<Flux<T>> in Spring MVC
Issue: SPR-15478
This commit is contained in:
@@ -18,6 +18,7 @@ package org.springframework.web.servlet.mvc.method.annotation;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -31,6 +32,7 @@ import org.reactivestreams.Subscription;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ReactiveAdapter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -116,14 +118,15 @@ class ReactiveTypeHandler {
|
||||
ReactiveAdapter adapter = this.reactiveRegistry.getAdapter(returnValue.getClass());
|
||||
Assert.state(adapter != null, "Unexpected return value: " + returnValue);
|
||||
|
||||
Class<?> elementType = returnType.nested().getNestedParameterType();
|
||||
|
||||
ResolvableType elementType = ResolvableType.forMethodParameter(returnType).getGeneric(0);
|
||||
Class<?> elementClass = elementType.resolve(Object.class);
|
||||
|
||||
Collection<MediaType> mediaTypes = getMediaTypes(request);
|
||||
Optional<MediaType> mediaType = mediaTypes.stream().filter(MimeType::isConcrete).findFirst();
|
||||
|
||||
if (adapter.isMultiValue()) {
|
||||
if (mediaTypes.stream().anyMatch(MediaType.TEXT_EVENT_STREAM::includes) ||
|
||||
ServerSentEvent.class.isAssignableFrom(elementType)) {
|
||||
ServerSentEvent.class.isAssignableFrom(elementClass)) {
|
||||
SseEmitter emitter = new SseEmitter();
|
||||
new SseEmitterSubscriber(emitter, this.taskExecutor).connect(adapter, returnValue);
|
||||
return emitter;
|
||||
@@ -133,7 +136,7 @@ class ReactiveTypeHandler {
|
||||
new JsonEmitterSubscriber(emitter, this.taskExecutor).connect(adapter, returnValue);
|
||||
return emitter;
|
||||
}
|
||||
if (CharSequence.class.isAssignableFrom(elementType) && !isJsonStringArray(elementType, mediaType)) {
|
||||
if (CharSequence.class.isAssignableFrom(elementClass) && !isJsonStringArray(elementClass, mediaType)) {
|
||||
ResponseBodyEmitter emitter = getEmitter(mediaType.orElse(MediaType.TEXT_PLAIN));
|
||||
new TextEmitterSubscriber(emitter, this.taskExecutor).connect(adapter, returnValue);
|
||||
return emitter;
|
||||
@@ -142,7 +145,7 @@ class ReactiveTypeHandler {
|
||||
|
||||
// Not streaming...
|
||||
DeferredResult<Object> result = new DeferredResult<>();
|
||||
new DeferredResultSubscriber(result, adapter).connect(adapter, returnValue);
|
||||
new DeferredResultSubscriber(result, adapter, elementType).connect(adapter, returnValue);
|
||||
WebAsyncUtils.getAsyncManager(request).startDeferredResultProcessing(result, mav);
|
||||
|
||||
return null;
|
||||
@@ -389,12 +392,15 @@ class ReactiveTypeHandler {
|
||||
|
||||
private final boolean multiValueSource;
|
||||
|
||||
private final CollectedValuesList values = new CollectedValuesList();
|
||||
private final CollectedValuesList values;
|
||||
|
||||
|
||||
DeferredResultSubscriber(DeferredResult<Object> result, ReactiveAdapter adapter) {
|
||||
DeferredResultSubscriber(DeferredResult<Object> result, ReactiveAdapter adapter,
|
||||
ResolvableType elementType) {
|
||||
|
||||
this.result = result;
|
||||
this.multiValueSource = adapter.isMultiValue();
|
||||
this.values = new CollectedValuesList(elementType);
|
||||
}
|
||||
|
||||
|
||||
@@ -435,6 +441,16 @@ class ReactiveTypeHandler {
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
static class CollectedValuesList extends ArrayList<Object> {
|
||||
|
||||
private final ResolvableType elementType;
|
||||
|
||||
CollectedValuesList(ResolvableType elementType) {
|
||||
this.elementType = elementType;
|
||||
}
|
||||
|
||||
public ResolvableType getReturnType() {
|
||||
return ResolvableType.forClassWithGenerics(List.class, this.elementType);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
@@ -28,6 +27,7 @@ import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
@@ -249,10 +249,9 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
|
||||
public ConcurrentResultMethodParameter(Object returnValue) {
|
||||
super(-1);
|
||||
this.returnValue = returnValue;
|
||||
ResolvableType candidateReturnType =
|
||||
ResolvableType.forType(super.getGenericParameterType()).getGeneric(0);
|
||||
this.returnType = (returnValue instanceof ReactiveTypeHandler.CollectedValuesList ?
|
||||
ResolvableType.forClassWithGenerics(List.class, candidateReturnType) : candidateReturnType);
|
||||
((ReactiveTypeHandler.CollectedValuesList) returnValue).getReturnType() :
|
||||
ResolvableType.forType(super.getGenericParameterType()).getGeneric(0));
|
||||
}
|
||||
|
||||
public ConcurrentResultMethodParameter(ConcurrentResultMethodParameter original) {
|
||||
@@ -277,6 +276,17 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
|
||||
return this.returnType.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Annotation> boolean hasMethodAnnotation(Class<T> annotationType) {
|
||||
|
||||
// Ensure @ResponseBody-style handling for values collected from a reactive type
|
||||
// even if actual return type is ResponseEntity<Flux<T>>
|
||||
|
||||
return ResponseBody.class.equals(annotationType) &&
|
||||
this.returnValue instanceof ReactiveTypeHandler.CollectedValuesList ||
|
||||
super.hasMethodAnnotation(annotationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConcurrentResultMethodParameter clone() {
|
||||
return new ConcurrentResultMethodParameter(this);
|
||||
|
||||
Reference in New Issue
Block a user