From 8c7658144284f2f47124c6ac8131f53320091551 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Sun, 3 Jul 2016 21:36:18 -0400 Subject: [PATCH] Provide rich type information to ConversionService When using the ConversionService to check and bridge to and from reactive types we now generallly provide the full type information available from method signatures. However that full type information is not always necessary such as when we perform additional checks on the generics of the reactive type (e.g. Mono). This allows us to switch to use DefaultFormattingConversionService instead of GenericConversionService while also ensuring that the CollectionToObjectConverter doesn't think it can convert List to any reactive type. The ObjectToObjectConverter can also interfere because it is smart enough to find the "from(Publisher)" method on Flux and Mono. To make up for that on the response side we now check if a type is assignable to Publisher first in which case it is a simple cast. In turn that means we don't need a PublisherToFluxConverter which can be problematic in its own right because it can convert from Mono to Flux which technically doesn't lose data but switches stream semantics. Issue: #124, #128 --- .../support/PublisherToFluxConverter.java | 51 --------------- .../config/WebReactiveConfiguration.java | 37 +++++------ .../reactive/result/SimpleResultHandler.java | 35 ++++++----- ...AbstractMessageConverterResultHandler.java | 45 ++++++++----- .../RequestBodyArgumentResolver.java | 15 +++-- .../annotation/ResponseBodyResultHandler.java | 3 +- .../ResponseEntityResultHandler.java | 18 ++++-- .../result/SimpleResultHandlerTests.java | 23 ++++--- .../MessageConverterResultHandlerTests.java | 63 ++++++++++++++----- .../RequestBodyArgumentResolverTests.java | 16 +++-- .../ResponseBodyResultHandlerTests.java | 7 +-- .../ResponseEntityResultHandlerTests.java | 7 +-- .../ViewResolutionResultHandlerTests.java | 12 ++-- 13 files changed, 177 insertions(+), 155 deletions(-) delete mode 100644 spring-web-reactive/src/main/java/org/springframework/core/convert/support/PublisherToFluxConverter.java diff --git a/spring-web-reactive/src/main/java/org/springframework/core/convert/support/PublisherToFluxConverter.java b/spring-web-reactive/src/main/java/org/springframework/core/convert/support/PublisherToFluxConverter.java deleted file mode 100644 index 6867245c03..0000000000 --- a/spring-web-reactive/src/main/java/org/springframework/core/convert/support/PublisherToFluxConverter.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2002-2015 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.core.convert.support; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; - -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.core.convert.converter.GenericConverter; - -/** - * @author Sebastien Deleuze - */ -public class PublisherToFluxConverter implements GenericConverter { - - @Override - public Set getConvertibleTypes() { - Set pairs = new LinkedHashSet<>(); - pairs.add(new ConvertiblePair(Publisher.class, Flux.class)); - return pairs; - } - - @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { - if (source == null) { - return null; - } - else if (Publisher.class.isAssignableFrom(sourceType.getType())) { - return Flux.from((Publisher)source); - } - return null; - } - -} diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java index ccf0e53af5..65cf61f880 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java @@ -28,25 +28,25 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.codec.Decoder; -import org.springframework.core.codec.Encoder; import org.springframework.core.codec.ByteBufferDecoder; import org.springframework.core.codec.ByteBufferEncoder; -import org.springframework.core.convert.support.PublisherToFluxConverter; +import org.springframework.core.codec.Decoder; +import org.springframework.core.codec.Encoder; +import org.springframework.core.codec.StringDecoder; +import org.springframework.core.codec.StringEncoder; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.support.MonoToCompletableFutureConverter; +import org.springframework.core.convert.support.ReactorToRxJava1Converter; +import org.springframework.format.Formatter; +import org.springframework.format.FormatterRegistry; +import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.format.support.FormattingConversionService; +import org.springframework.http.MediaType; +import org.springframework.http.codec.SseEventEncoder; import org.springframework.http.codec.json.JacksonJsonDecoder; import org.springframework.http.codec.json.JacksonJsonEncoder; import org.springframework.http.codec.xml.Jaxb2Decoder; import org.springframework.http.codec.xml.Jaxb2Encoder; -import org.springframework.core.codec.StringDecoder; -import org.springframework.core.codec.StringEncoder; -import org.springframework.core.convert.converter.Converter; -import org.springframework.core.convert.converter.ConverterRegistry; -import org.springframework.core.convert.support.GenericConversionService; -import org.springframework.core.convert.support.MonoToCompletableFutureConverter; -import org.springframework.core.convert.support.ReactorToRxJava1Converter; -import org.springframework.format.Formatter; -import org.springframework.http.MediaType; -import org.springframework.http.codec.SseEventEncoder; import org.springframework.http.converter.reactive.CodecHttpMessageConverter; import org.springframework.http.converter.reactive.HttpMessageConverter; import org.springframework.http.converter.reactive.ResourceHttpMessageConverter; @@ -271,17 +271,13 @@ public class WebReactiveConfiguration implements ApplicationContextAware { protected void extendMessageConverters(List> converters) { } - // TODO: switch to DefaultFormattingConversionService - @Bean - public GenericConversionService mvcConversionService() { - GenericConversionService service = new GenericConversionService(); + public FormattingConversionService mvcConversionService() { + FormattingConversionService service = new DefaultFormattingConversionService(); addFormatters(service); return service; } - // TODO: switch to FormatterRegistry - /** * Override to add custom {@link Converter}s and {@link Formatter}s. *

By default this method method registers: @@ -290,9 +286,8 @@ public class WebReactiveConfiguration implements ApplicationContextAware { *

  • {@link ReactorToRxJava1Converter} * */ - protected void addFormatters(ConverterRegistry registry) { + protected void addFormatters(FormatterRegistry registry) { registry.addConverter(new MonoToCompletableFutureConverter()); - registry.addConverter(new PublisherToFluxConverter()); if (DependencyUtils.hasRxJava1()) { registry.addConverter(new ReactorToRxJava1Converter()); } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/SimpleResultHandler.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/SimpleResultHandler.java index bff817cc2f..b3161c86e8 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/SimpleResultHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/SimpleResultHandler.java @@ -18,12 +18,14 @@ package org.springframework.web.reactive.result; import java.util.Optional; +import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.core.Ordered; import org.springframework.core.ResolvableType; import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.util.Assert; import org.springframework.web.reactive.HandlerResult; import org.springframework.web.reactive.HandlerResultHandler; @@ -42,6 +44,11 @@ import org.springframework.web.server.ServerWebExchange; */ public class SimpleResultHandler implements Ordered, HandlerResultHandler { + protected static final TypeDescriptor MONO_TYPE = TypeDescriptor.valueOf(Mono.class); + + protected static final TypeDescriptor FLUX_TYPE = TypeDescriptor.valueOf(Flux.class); + + private ConversionService conversionService; private int order = Ordered.LOWEST_PRECEDENCE; @@ -83,14 +90,19 @@ public class SimpleResultHandler implements Ordered, HandlerResultHandler { if (Void.TYPE.equals(type.getRawClass())) { return true; } - if (getConversionService().canConvert(type.getRawClass(), Mono.class) || - getConversionService().canConvert(type.getRawClass(), Flux.class)) { + TypeDescriptor source = new TypeDescriptor(result.getReturnTypeSource()); + if (Publisher.class.isAssignableFrom(type.getRawClass()) || + canConvert(source, MONO_TYPE) || canConvert(source, FLUX_TYPE)) { Class clazz = result.getReturnType().getGeneric(0).getRawClass(); return Void.class.equals(clazz); } return false; } + private boolean canConvert(TypeDescriptor source, TypeDescriptor target) { + return getConversionService().canConvert(source, target); + } + @SuppressWarnings("unchecked") @Override public Mono handleResult(ServerWebExchange exchange, HandlerResult result) { @@ -98,19 +110,14 @@ public class SimpleResultHandler implements Ordered, HandlerResultHandler { if (!optional.isPresent()) { return Mono.empty(); } - - Object returnValue = optional.get(); - if (returnValue instanceof Mono) { - return (Mono) returnValue; - } - - ResolvableType returnType = result.getReturnType(); - if (getConversionService().canConvert(returnType.getRawClass(), Mono.class)) { - return this.conversionService.convert(returnValue, Mono.class); - } - else { - return this.conversionService.convert(returnValue, Flux.class).single(); + Object value = optional.get(); + if (Publisher.class.isAssignableFrom(result.getReturnType().getRawClass())) { + return Mono.from((Publisher) value).then(); } + TypeDescriptor source = new TypeDescriptor(result.getReturnTypeSource()); + return canConvert(source, MONO_TYPE) ? + ((Mono) getConversionService().convert(value, source, MONO_TYPE)) : + ((Flux) getConversionService().convert(value, source, FLUX_TYPE)).single(); } } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageConverterResultHandler.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageConverterResultHandler.java index cf66171006..95f5ca57b4 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageConverterResultHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageConverterResultHandler.java @@ -22,8 +22,10 @@ import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.http.MediaType; import org.springframework.http.converter.reactive.HttpMessageConverter; import org.springframework.http.server.reactive.ServerHttpResponse; @@ -41,6 +43,11 @@ import org.springframework.web.server.ServerWebExchange; */ public abstract class AbstractMessageConverterResultHandler extends ContentNegotiatingResultHandlerSupport { + protected static final TypeDescriptor MONO_TYPE = TypeDescriptor.valueOf(Mono.class); + + protected static final TypeDescriptor FLUX_TYPE = TypeDescriptor.valueOf(Flux.class); + + private final List> messageConverters; @@ -70,28 +77,34 @@ public abstract class AbstractMessageConverterResultHandler extends ContentNegot @SuppressWarnings("unchecked") - protected Mono writeBody(ServerWebExchange exchange, Object body, ResolvableType bodyType) { + protected Mono writeBody(ServerWebExchange exchange, Object body, + ResolvableType bodyType, MethodParameter bodyTypeParameter) { - boolean convertToFlux = getConversionService().canConvert(bodyType.getRawClass(), Flux.class); - boolean convertToMono = getConversionService().canConvert(bodyType.getRawClass(), Mono.class); + Publisher publisher = null; + ResolvableType elementType; - ResolvableType elementType = convertToFlux || convertToMono ? bodyType.getGeneric(0) : bodyType; - - Publisher publisher; - if (body == null) { - publisher = Mono.empty(); - } - else if (convertToMono) { - publisher = getConversionService().convert(body, Mono.class); - } - else if (convertToFlux) { - publisher = getConversionService().convert(body, Flux.class); + if (Publisher.class.isAssignableFrom(bodyType.getRawClass())) { + publisher = (Publisher) body; } else { - publisher = Mono.just(body); + TypeDescriptor descriptor = new TypeDescriptor(bodyTypeParameter); + if (getConversionService().canConvert(descriptor, MONO_TYPE)) { + publisher = (Publisher) getConversionService().convert(body, descriptor, MONO_TYPE); + } + else if (getConversionService().canConvert(descriptor, FLUX_TYPE)) { + publisher = (Publisher) getConversionService().convert(body, descriptor, FLUX_TYPE); + } } - if (Void.class.equals(elementType.getRawClass())) { + if (publisher != null) { + elementType = bodyType.getGeneric(0); + } + else { + elementType = bodyType; + publisher = Mono.justOrEmpty(body); + } + + if (void.class == elementType.getRawClass() || Void.class == elementType.getRawClass()) { return Mono.from((Publisher) publisher); } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java index 3a7370778e..52ece42bbc 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java @@ -29,6 +29,7 @@ import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.http.MediaType; import org.springframework.http.converter.reactive.HttpMessageConverter; import org.springframework.http.server.reactive.ServerHttpRequest; @@ -57,6 +58,11 @@ import org.springframework.web.server.UnsupportedMediaTypeStatusException; */ public class RequestBodyArgumentResolver implements HandlerMethodArgumentResolver { + private static final TypeDescriptor MONO_TYPE = TypeDescriptor.valueOf(Mono.class); + + private static final TypeDescriptor FLUX_TYPE = TypeDescriptor.valueOf(Flux.class); + + private final List> messageConverters; private final ConversionService conversionService; @@ -123,8 +129,9 @@ public class RequestBodyArgumentResolver implements HandlerMethodArgumentResolve ResolvableType type = ResolvableType.forMethodParameter(parameter); - boolean convertFromMono = getConversionService().canConvert(Mono.class, type.getRawClass()); - boolean convertFromFlux = getConversionService().canConvert(Flux.class, type.getRawClass()); + TypeDescriptor typeDescriptor = new TypeDescriptor(parameter); + boolean convertFromMono = getConversionService().canConvert(MONO_TYPE, typeDescriptor); + boolean convertFromFlux = getConversionService().canConvert(FLUX_TYPE, typeDescriptor); ResolvableType elementType = convertFromMono || convertFromFlux ? type.getGeneric(0) : type; @@ -141,7 +148,7 @@ public class RequestBodyArgumentResolver implements HandlerMethodArgumentResolve if (this.validator != null) { flux = flux.map(applyValidationIfApplicable(parameter)); } - return Mono.just(this.conversionService.convert(flux, type.getRawClass())); + return Mono.just(getConversionService().convert(flux, FLUX_TYPE, typeDescriptor)); } else { Mono mono = converter.readOne(elementType, request); @@ -151,7 +158,7 @@ public class RequestBodyArgumentResolver implements HandlerMethodArgumentResolve if (!convertFromMono) { return mono.map(value-> value); // TODO: MonoToObjectConverter } - return Mono.just(this.conversionService.convert(mono, type.getRawClass())); + return Mono.just(getConversionService().convert(mono, MONO_TYPE, typeDescriptor)); } } } 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 0eb04f7bdb..54cbbcc85e 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 @@ -114,7 +114,8 @@ public class ResponseBodyResultHandler extends AbstractMessageConverterResultHan public Mono handleResult(ServerWebExchange exchange, HandlerResult result) { Object body = result.getReturnValue().orElse(null); ResolvableType bodyType = result.getReturnType(); - return writeBody(exchange, body, bodyType); + MethodParameter bodyTypeParameter = result.getReturnTypeSource(); + return writeBody(exchange, body, bodyType, bodyTypeParameter); } } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java index 5ebc8f8cfa..2079db0bd5 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java @@ -20,6 +20,7 @@ import java.util.Optional; import reactor.core.publisher.Mono; +import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpEntity; @@ -85,7 +86,6 @@ public class ResponseEntityResultHandler extends AbstractMessageConverterResultH else if (getConversionService().canConvert(returnType.getRawClass(), Mono.class)) { ResolvableType genericType = result.getReturnType().getGeneric(0); return isSupportedType(genericType); - } return false; } @@ -100,17 +100,25 @@ public class ResponseEntityResultHandler extends AbstractMessageConverterResultH public Mono handleResult(ServerWebExchange exchange, HandlerResult result) { ResolvableType returnType = result.getReturnType(); - Mono returnValueMono; - ResolvableType bodyType; + ResolvableType bodyType; + MethodParameter bodyTypeParameter; + + Mono returnValueMono; Optional optional = result.getReturnValue(); + if (optional.isPresent() && getConversionService().canConvert(returnType.getRawClass(), Mono.class)) { returnValueMono = getConversionService().convert(optional.get(), Mono.class); - bodyType = returnType.getGeneric(0).getGeneric(0); + bodyType = returnType.getGeneric(0, 0); + bodyTypeParameter = new MethodParameter(result.getReturnTypeSource()); + bodyTypeParameter.increaseNestingLevel(); + bodyTypeParameter.increaseNestingLevel(); } else { returnValueMono = Mono.justOrEmpty(optional); bodyType = returnType.getGeneric(0); + bodyTypeParameter = new MethodParameter(result.getReturnTypeSource()); + bodyTypeParameter.increaseNestingLevel(); } return returnValueMono.then(returnValue -> { @@ -132,7 +140,7 @@ public class ResponseEntityResultHandler extends AbstractMessageConverterResultH .forEach(entry -> responseHeaders.put(entry.getKey(), entry.getValue())); } - return writeBody(exchange, httpEntity.getBody(), bodyType); + return writeBody(exchange, httpEntity.getBody(), bodyType, bodyTypeParameter); }); } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/SimpleResultHandlerTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/SimpleResultHandlerTests.java index 0ebeddfa67..7aba79ee55 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/SimpleResultHandlerTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/SimpleResultHandlerTests.java @@ -16,6 +16,7 @@ package org.springframework.web.reactive.result; +import java.util.List; import java.util.concurrent.CompletableFuture; import org.junit.Before; @@ -26,10 +27,10 @@ import rx.Observable; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; -import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.convert.support.MonoToCompletableFutureConverter; -import org.springframework.core.convert.support.PublisherToFluxConverter; import org.springframework.core.convert.support.ReactorToRxJava1Converter; +import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.format.support.FormattingConversionService; import org.springframework.web.reactive.HandlerResult; import static org.junit.Assert.assertEquals; @@ -46,16 +47,15 @@ public class SimpleResultHandlerTests { @Before public void setUp() throws Exception { - GenericConversionService conversionService = new GenericConversionService(); - conversionService.addConverter(new MonoToCompletableFutureConverter()); - conversionService.addConverter(new PublisherToFluxConverter()); - conversionService.addConverter(new ReactorToRxJava1Converter()); - this.resultHandler = new SimpleResultHandler(conversionService); + FormattingConversionService service = new DefaultFormattingConversionService(); + service.addConverter(new MonoToCompletableFutureConverter()); + service.addConverter(new ReactorToRxJava1Converter()); + this.resultHandler = new SimpleResultHandler(service); } @Test - public void supportsWithConversionService() throws NoSuchMethodException { + public void supports() throws NoSuchMethodException { testSupports(ResolvableType.forClass(void.class), true); testSupports(ResolvableType.forClassWithGenerics(Publisher.class, Void.class), true); testSupports(ResolvableType.forClassWithGenerics(Flux.class, Void.class), true); @@ -66,6 +66,11 @@ public class SimpleResultHandlerTests { testSupports(ResolvableType.forClassWithGenerics(Publisher.class, String.class), false); } + @Test + public void supportsUsesGenericTypeInformation() throws Exception { + testSupports(ResolvableType.forClassWithGenerics(List.class, Void.class), false); + } + private void testSupports(ResolvableType type, boolean result) { MethodParameter param = ResolvableMethod.on(TestController.class).returning(type).resolveReturnType(); HandlerResult handlerResult = new HandlerResult(new TestController(), null, param); @@ -90,6 +95,8 @@ public class SimpleResultHandlerTests { public Publisher publisher() { return null; } + public List list() { return null; } + } } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageConverterResultHandlerTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageConverterResultHandlerTests.java index 62d065193f..cde0c86a90 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageConverterResultHandlerTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageConverterResultHandlerTests.java @@ -36,12 +36,12 @@ import reactor.core.publisher.Mono; import reactor.core.test.TestSubscriber; import rx.Observable; +import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.core.codec.ByteBufferEncoder; import org.springframework.core.codec.StringEncoder; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.convert.support.MonoToCompletableFutureConverter; -import org.springframework.core.convert.support.PublisherToFluxConverter; import org.springframework.core.convert.support.ReactorToRxJava1Converter; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; @@ -58,6 +58,7 @@ import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.ObjectUtils; import org.springframework.web.reactive.accept.RequestedContentTypeResolver; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; +import org.springframework.web.reactive.result.ResolvableMethod; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.adapter.DefaultServerWebExchange; import org.springframework.web.server.session.MockWebSessionManager; @@ -92,8 +93,8 @@ public class MessageConverterResultHandlerTests { @Test // SPR-12894 public void useDefaultContentType() throws Exception { Resource body = new ClassPathResource("logo.png", getClass()); - ResolvableType bodyType = ResolvableType.forType(Resource.class); - this.resultHandler.writeBody(this.exchange, body, bodyType).block(Duration.ofSeconds(5)); + ResolvableType type = ResolvableType.forType(Resource.class); + this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5)); assertEquals("image/x-png", this.response.getHeaders().getFirst("Content-Type")); } @@ -104,22 +105,22 @@ public class MessageConverterResultHandlerTests { Collections.singleton(APPLICATION_JSON)); String body = "foo"; - ResolvableType bodyType = ResolvableType.forType(String.class); - this.resultHandler.writeBody(this.exchange, body, bodyType).block(Duration.ofSeconds(5)); + ResolvableType type = ResolvableType.forType(String.class); + this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5)); assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType()); } @Test public void voidReturnType() throws Exception { - testVoidReturnType(null, ResolvableType.forType(Void.class)); + testVoidReturnType(null, ResolvableType.forType(void.class)); testVoidReturnType(Mono.empty(), ResolvableType.forClassWithGenerics(Mono.class, Void.class)); testVoidReturnType(Flux.empty(), ResolvableType.forClassWithGenerics(Flux.class, Void.class)); testVoidReturnType(Observable.empty(), ResolvableType.forClassWithGenerics(Observable.class, Void.class)); } - private void testVoidReturnType(Object body, ResolvableType bodyType) { - this.resultHandler.writeBody(this.exchange, body, bodyType).block(Duration.ofSeconds(5)); + private void testVoidReturnType(Object body, ResolvableType type) { + this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5)); assertNull(this.response.getHeaders().get("Content-Type")); assertNull(this.response.getBody()); @@ -128,10 +129,10 @@ public class MessageConverterResultHandlerTests { @Test // SPR-13135 public void unsupportedReturnType() throws Exception { ByteArrayOutputStream body = new ByteArrayOutputStream(); - ResolvableType bodyType = ResolvableType.forType(OutputStream.class); + ResolvableType type = ResolvableType.forType(OutputStream.class); HttpMessageConverter converter = new CodecHttpMessageConverter<>(new ByteBufferEncoder()); - Mono mono = createResultHandler(converter).writeBody(this.exchange, body, bodyType); + Mono mono = createResultHandler(converter).writeBody(this.exchange, body, type, returnType(type)); TestSubscriber.subscribe(mono).assertError(IllegalStateException.class); } @@ -139,8 +140,8 @@ public class MessageConverterResultHandlerTests { @Test // SPR-12811 public void jacksonTypeOfListElement() throws Exception { List body = Arrays.asList(new Foo("foo"), new Bar("bar")); - ResolvableType bodyType = ResolvableType.forClassWithGenerics(List.class, ParentClass.class); - this.resultHandler.writeBody(this.exchange, body, bodyType).block(Duration.ofSeconds(5)); + ResolvableType type = ResolvableType.forClassWithGenerics(List.class, ParentClass.class); + this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5)); assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType()); assertResponseBody("[{\"type\":\"foo\",\"parentProperty\":\"foo\"}," + @@ -151,8 +152,8 @@ public class MessageConverterResultHandlerTests { @Ignore public void jacksonTypeWithSubType() throws Exception { SimpleBean body = new SimpleBean(123L, "foo"); - ResolvableType bodyType = ResolvableType.forClass(Identifiable.class); - this.resultHandler.writeBody(this.exchange, body, bodyType).block(Duration.ofSeconds(5)); + ResolvableType type = ResolvableType.forClass(Identifiable.class); + this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5)); assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType()); assertResponseBody("{\"id\":123,\"name\":\"foo\"}"); @@ -162,14 +163,18 @@ public class MessageConverterResultHandlerTests { @Ignore public void jacksonTypeWithSubTypeOfListElement() throws Exception { List body = Arrays.asList(new SimpleBean(123L, "foo"), new SimpleBean(456L, "bar")); - ResolvableType bodyType = ResolvableType.forClassWithGenerics(List.class, Identifiable.class); - this.resultHandler.writeBody(this.exchange, body, bodyType).block(Duration.ofSeconds(5)); + ResolvableType type = ResolvableType.forClassWithGenerics(List.class, Identifiable.class); + this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5)); assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType()); assertResponseBody("[{\"id\":123,\"name\":\"foo\"},{\"id\":456,\"name\":\"bar\"}]"); } + private MethodParameter returnType(ResolvableType bodyType) { + return ResolvableMethod.on(TestController.class).returning(bodyType).resolveReturnType(); + } + private AbstractMessageConverterResultHandler createResultHandler(HttpMessageConverter... converters) { List> converterList; if (ObjectUtils.isEmpty(converters)) { @@ -186,7 +191,6 @@ public class MessageConverterResultHandlerTests { GenericConversionService service = new GenericConversionService(); service.addConverter(new MonoToCompletableFutureConverter()); - service.addConverter(new PublisherToFluxConverter()); service.addConverter(new ReactorToRxJava1Converter()); RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build(); @@ -267,4 +271,29 @@ public class MessageConverterResultHandlerTests { } } + @SuppressWarnings("unused") + private static class TestController { + + Resource resource() { return null; } + + String string() { return null; } + + void voidReturn() { } + + Mono monoVoid() { return null; } + + Flux fluxVoid() { return null; } + + Observable observableVoid() { return null; } + + OutputStream outputStream() { return null; } + + List listParentClass() { return null; } + + Identifiable identifiable() { return null; } + + List listIdentifiable() { return null; } + + } + } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java index 5e9ecb720f..d0171d69da 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java @@ -42,16 +42,16 @@ import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.annotation.SynthesizingMethodParameter; import org.springframework.core.codec.Decoder; -import org.springframework.core.convert.support.PublisherToFluxConverter; -import org.springframework.http.codec.json.JacksonJsonDecoder; import org.springframework.core.codec.StringDecoder; -import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.convert.support.MonoToCompletableFutureConverter; import org.springframework.core.convert.support.ReactorToRxJava1Converter; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.format.support.FormattingConversionService; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.http.codec.json.JacksonJsonDecoder; import org.springframework.http.converter.reactive.CodecHttpMessageConverter; import org.springframework.http.converter.reactive.HttpMessageConverter; import org.springframework.http.server.reactive.MockServerHttpRequest; @@ -202,23 +202,29 @@ public class RequestBodyArgumentResolverTests { @SuppressWarnings("unchecked") private T resolveValue(String paramName, Class valueType, String body) { + this.request.getHeaders().setContentType(MediaType.APPLICATION_JSON); this.request.writeWith(Flux.just(dataBuffer(body))); + Mono result = this.resolver.resolveArgument(parameter(paramName), this.model, this.exchange); Object value = result.block(Duration.ofSeconds(5)); + assertNotNull(value); assertTrue("Actual type: " + value.getClass(), valueType.isAssignableFrom(value.getClass())); + return (T) value; } @SuppressWarnings("Convert2MethodRef") private RequestBodyArgumentResolver resolver(Decoder... decoders) { + List> converters = new ArrayList<>(); Arrays.asList(decoders).forEach(decoder -> converters.add(new CodecHttpMessageConverter<>(decoder))); - GenericConversionService service = new GenericConversionService(); + + FormattingConversionService service = new DefaultFormattingConversionService(); service.addConverter(new MonoToCompletableFutureConverter()); - service.addConverter(new PublisherToFluxConverter()); service.addConverter(new ReactorToRxJava1Converter()); + return new RequestBodyArgumentResolver(converters, service, new TestBeanValidator()); } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java index 38da2644c9..06efa14061 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java @@ -28,10 +28,10 @@ import reactor.core.publisher.Mono; import org.springframework.core.codec.ByteBufferEncoder; import org.springframework.core.codec.StringEncoder; import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.convert.support.MonoToCompletableFutureConverter; -import org.springframework.core.convert.support.PublisherToFluxConverter; import org.springframework.core.convert.support.ReactorToRxJava1Converter; +import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.format.support.FormattingConversionService; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.http.codec.json.JacksonJsonEncoder; @@ -99,9 +99,8 @@ public class ResponseBodyResultHandlerTests { else { converterList = Arrays.asList(converters); } - GenericConversionService service = new GenericConversionService(); + FormattingConversionService service = new DefaultFormattingConversionService(); service.addConverter(new MonoToCompletableFutureConverter()); - service.addConverter(new PublisherToFluxConverter()); service.addConverter(new ReactorToRxJava1Converter()); RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build(); diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java index c90ddb8334..b54994f170 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java @@ -33,11 +33,11 @@ import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.core.codec.ByteBufferEncoder; import org.springframework.core.codec.StringEncoder; -import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.convert.support.MonoToCompletableFutureConverter; -import org.springframework.core.convert.support.PublisherToFluxConverter; import org.springframework.core.convert.support.ReactorToRxJava1Converter; import org.springframework.core.io.buffer.support.DataBufferTestUtils; +import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.format.support.FormattingConversionService; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -102,9 +102,8 @@ public class ResponseEntityResultHandlerTests { else { converterList = Arrays.asList(converters); } - GenericConversionService service = new GenericConversionService(); + FormattingConversionService service = new DefaultFormattingConversionService(); service.addConverter(new MonoToCompletableFutureConverter()); - service.addConverter(new PublisherToFluxConverter()); service.addConverter(new ReactorToRxJava1Converter()); diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java index 4f8b7ed511..03b6581a24 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java @@ -38,13 +38,13 @@ import rx.Single; import org.springframework.core.MethodParameter; import org.springframework.core.Ordered; import org.springframework.core.ResolvableType; -import org.springframework.core.convert.support.ConfigurableConversionService; -import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.core.convert.support.PublisherToFluxConverter; +import org.springframework.core.convert.support.MonoToCompletableFutureConverter; import org.springframework.core.convert.support.ReactorToRxJava1Converter; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.core.io.buffer.support.DataBufferTestUtils; +import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.format.support.FormattingConversionService; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.MockServerHttpRequest; @@ -264,10 +264,12 @@ public class ViewResolutionResultHandlerTests { } private ViewResolutionResultHandler createResultHandler(List defaultViews, ViewResolver... resolvers) { - ConfigurableConversionService service = new DefaultConversionService(); + + FormattingConversionService service = new DefaultFormattingConversionService(); + service.addConverter(new MonoToCompletableFutureConverter()); service.addConverter(new ReactorToRxJava1Converter()); - service.addConverter(new PublisherToFluxConverter()); List resolverList = Arrays.asList(resolvers); + ViewResolutionResultHandler handler = new ViewResolutionResultHandler(resolverList, service); handler.setDefaultViews(defaultViews); return handler;