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;