diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/ContentNegotiatingResultHandlerSupport.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/ContentNegotiatingResultHandlerSupport.java
new file mode 100644
index 00000000000..fd0b0fb4b7f
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/ContentNegotiatingResultHandlerSupport.java
@@ -0,0 +1,148 @@
+/*
+ * 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.reactive.result;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import org.springframework.core.Ordered;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.http.MediaType;
+import org.springframework.util.Assert;
+import org.springframework.web.reactive.HandlerMapping;
+import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * Base class for {@link org.springframework.web.reactive.HandlerResultHandler
+ * HandlerResultHandler} implementations that perform content negotiation.
+ *
+ * @author Rossen Stoyanchev
+ */
+public abstract class ContentNegotiatingResultHandlerSupport implements Ordered {
+
+ private static final MediaType MEDIA_TYPE_APPLICATION_ALL = new MediaType("application");
+
+
+ private final ConversionService conversionService;
+
+ private final RequestedContentTypeResolver contentTypeResolver;
+
+ private int order = LOWEST_PRECEDENCE;
+
+
+ protected ContentNegotiatingResultHandlerSupport(ConversionService conversionService,
+ RequestedContentTypeResolver contentTypeResolver) {
+
+ Assert.notNull(conversionService, "'conversionService' is required.");
+ Assert.notNull(contentTypeResolver, "'contentTypeResolver' is required.");
+ this.conversionService = conversionService;
+ this.contentTypeResolver = contentTypeResolver;
+ }
+
+
+ /**
+ * Return the configured {@link ConversionService}.
+ */
+ public ConversionService getConversionService() {
+ return this.conversionService;
+ }
+
+ /**
+ * Return the configured {@link RequestedContentTypeResolver}.
+ */
+ public RequestedContentTypeResolver getContentTypeResolver() {
+ return this.contentTypeResolver;
+ }
+
+ /**
+ * Set the order for this result handler relative to others.
+ *
By default set to {@link Ordered#LOWEST_PRECEDENCE}, however see
+ * Javadoc of sub-classes which may change this default.
+ * @param order the order
+ */
+ public void setOrder(int order) {
+ this.order = order;
+ }
+
+ @Override
+ public int getOrder() {
+ return this.order;
+ }
+
+
+ /**
+ * Select the best media type for the current request through a content
+ * negotiation algorithm.
+ * @param exchange the current request
+ * @param producibleTypes the media types that can be produced for the current request
+ * @return the selected media type or {@code null}
+ */
+ protected MediaType selectMediaType(ServerWebExchange exchange, List producibleTypes) {
+
+ List acceptableTypes = getAcceptableTypes(exchange);
+ producibleTypes = getProducibleTypes(exchange, producibleTypes);
+
+ Set compatibleMediaTypes = new LinkedHashSet<>();
+ for (MediaType acceptable : acceptableTypes) {
+ for (MediaType producible : producibleTypes) {
+ if (acceptable.isCompatibleWith(producible)) {
+ compatibleMediaTypes.add(selectMoreSpecificMediaType(acceptable, producible));
+ }
+ }
+ }
+
+ List result = new ArrayList<>(compatibleMediaTypes);
+ MediaType.sortBySpecificityAndQuality(result);
+
+ for (MediaType mediaType : compatibleMediaTypes) {
+ if (mediaType.isConcrete()) {
+ return mediaType;
+ }
+ else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION_ALL)) {
+ return MediaType.APPLICATION_OCTET_STREAM;
+ }
+ }
+
+ return null;
+ }
+
+ private List getAcceptableTypes(ServerWebExchange exchange) {
+ List mediaTypes = this.contentTypeResolver.resolveMediaTypes(exchange);
+ return (mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes);
+ }
+
+ private List getProducibleTypes(ServerWebExchange exchange, List mediaTypes) {
+ Optional> optional = exchange.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
+ if (optional.isPresent()) {
+ Set set = (Set) optional.get();
+ return new ArrayList<>(set);
+ }
+ return mediaTypes;
+ }
+
+ private MediaType selectMoreSpecificMediaType(MediaType acceptable, MediaType producible) {
+ producible = producible.copyQualityValue(acceptable);
+ Comparator comparator = MediaType.SPECIFICITY_COMPARATOR;
+ return (comparator.compare(acceptable, producible) <= 0 ? acceptable : producible);
+ }
+
+}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java
index 8a64f6c86ce..a2107aa77db 100644
--- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java
@@ -16,13 +16,8 @@
package org.springframework.web.reactive.result.method.annotation;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
-import java.util.Set;
import java.util.stream.Collectors;
import org.reactivestreams.Publisher;
@@ -39,11 +34,11 @@ import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
-import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.HandlerResultHandler;
import org.springframework.web.reactive.accept.HeaderContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
+import org.springframework.web.reactive.result.ContentNegotiatingResultHandlerSupport;
import org.springframework.web.server.NotAcceptableStatusException;
import org.springframework.web.server.ServerWebExchange;
@@ -53,25 +48,21 @@ import org.springframework.web.server.ServerWebExchange;
* with {@code @ResponseBody} writing to the body of the request or response with
* an {@link HttpMessageConverter}.
*
+ *
By default the order for the result handler is set to 0. It is generally
+ * safe and expected it will be ordered ahead of other result handlers since it
+ * only gets involved based on the presence of an {@code @ResponseBody}
+ * annotation.
+ *
* @author Rossen Stoyanchev
* @author Stephane Maldini
* @author Sebastien Deleuze
* @author Arjen Poutsma
*/
-public class ResponseBodyResultHandler implements HandlerResultHandler, Ordered {
-
- private static final MediaType MEDIA_TYPE_APPLICATION_ALL = new MediaType("application");
+public class ResponseBodyResultHandler extends ContentNegotiatingResultHandlerSupport
+ implements HandlerResultHandler, Ordered {
private final List> messageConverters;
- private final ConversionService conversionService;
-
- private final RequestedContentTypeResolver contentTypeResolver;
-
- private final List supportedMediaTypes;
-
- private int order = 0;
-
/**
* Constructor with message converters and a {@code ConversionService} only
@@ -91,46 +82,18 @@ public class ResponseBodyResultHandler implements HandlerResultHandler, Ordered
* Constructor with message converters, a {@code ConversionService}, and a
* {@code RequestedContentTypeResolver}.
*
- * @param messageConverters converters for writing the response body with
+ * @param converters converters for writing the response body with
* @param conversionService for converting other reactive types (e.g.
* rx.Observable, rx.Single, etc.) to Flux or Mono
+ * @param contentTypeResolver for resolving the requested content type
*/
- public ResponseBodyResultHandler(List> messageConverters,
+ public ResponseBodyResultHandler(List> converters,
ConversionService conversionService, RequestedContentTypeResolver contentTypeResolver) {
- Assert.notEmpty(messageConverters, "At least one message converter is required.");
- Assert.notNull(conversionService, "'conversionService' is required.");
- Assert.notNull(contentTypeResolver, "'contentTypeResolver' is required.");
-
- this.messageConverters = messageConverters;
- this.conversionService = conversionService;
- this.contentTypeResolver = contentTypeResolver;
- this.supportedMediaTypes = initSupportedMediaTypes(messageConverters);
- }
-
- private static List initSupportedMediaTypes(List> converters) {
- Set set = new LinkedHashSet<>();
- converters.forEach(converter -> set.addAll(converter.getWritableMediaTypes()));
- List result = new ArrayList<>(set);
- MediaType.sortBySpecificity(result);
- return Collections.unmodifiableList(result);
- }
-
-
- /**
- * Set the order for this result handler relative to others.
- *
By default this is set to 0 and is generally save to be ahead of other
- * result handlers since it only gets involved if the method (or class) is
- * annotated with {@code @ResponseBody}.
- * @param order the order
- */
- public void setOrder(int order) {
- this.order = order;
- }
-
- @Override
- public int getOrder() {
- return this.order;
+ super(conversionService, contentTypeResolver);
+ Assert.notEmpty(converters, "At least one message converter is required.");
+ this.messageConverters = converters;
+ setOrder(0);
}
@@ -154,10 +117,10 @@ public class ResponseBodyResultHandler implements HandlerResultHandler, Ordered
ResolvableType elementType;
ResolvableType returnType = result.getReturnValueType();
- if (this.conversionService.canConvert(returnType.getRawClass(), Publisher.class)) {
+ if (getConversionService().canConvert(returnType.getRawClass(), Publisher.class)) {
Optional