Add ReactiveAdapterRegistry

Issue: SPR-14159
This commit is contained in:
Rossen Stoyanchev 2016-07-22 00:17:13 -04:00
parent 1a2ac8ea56
commit 101220bad1
29 changed files with 775 additions and 635 deletions

View File

@ -0,0 +1,116 @@
/*
* 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.core;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* Contract for adapting to and from {@link Flux} and {@link Mono}.
*
* <p>An adapter supports a specific adaptee type whose stream semantics can be
* checked via {@link #getDescriptor()}.
*
* <p>Use the {@link ReactiveAdapterRegistry} to obtain an adapter for a
* supported adaptee type or to register additional adapters.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public interface ReactiveAdapter {
/**
* Return a descriptor with further information about the adaptee.
*/
Descriptor getDescriptor();
/**
* Adapt the given Object to a {@link Mono}
* @param source the source object to adapt
* @return the resulting {@link Mono} possibly empty
*/
<T> Mono<T> toMono(Object source);
/**
* Adapt the given Object to a {@link Flux}.
* @param source the source object to adapt
* @return the resulting {@link Flux} possibly empty
*/
<T> Flux<T> toFlux(Object source);
/**
* Adapt the given Object to a Publisher.
* @param source the source object to adapt
* @return the resulting {@link Mono} or {@link Flux} possibly empty
*/
<T> Publisher<T> toPublisher(Object source);
/**
* Adapt the given Publisher to the target adaptee.
* @param publisher the publisher to adapt
* @return the resulting adaptee
*/
Object fromPublisher(Publisher<?> publisher);
/**
* A descriptor with information about the adaptee stream semantics.
*/
class Descriptor {
private final boolean isMultiValue;
private final boolean supportsEmpty;
private final boolean isNoValue;
public Descriptor(boolean isMultiValue, boolean canBeEmpty, boolean isNoValue) {
this.isMultiValue = isMultiValue;
this.supportsEmpty = canBeEmpty;
this.isNoValue = isNoValue;
}
/**
* Return {@code true} if the adaptee implies 0..N values can be produced
* and is therefore a good fit to adapt to {@link Flux}. A {@code false}
* return value implies the adaptee will produce 1 value at most and is
* therefore a good fit for {@link Mono}.
*/
public boolean isMultiValue() {
return this.isMultiValue;
}
/**
* Return {@code true} if the adaptee can complete without values.
*/
public boolean supportsEmpty() {
return this.supportsEmpty;
}
/**
* Return {@code true} if the adaptee implies no values will be produced,
* i.e. providing only completion or error signal.
*/
public boolean isNoValue() {
return this.isNoValue;
}
}
}

View File

@ -0,0 +1,282 @@
/*
* 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.core;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.function.Predicate;
import org.reactivestreams.Publisher;
import reactor.adapter.RxJava1Adapter;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import rx.Completable;
import rx.Observable;
import rx.Single;
import org.springframework.core.ReactiveAdapter.Descriptor;
import org.springframework.util.ClassUtils;
/**
* A registry of adapters to adapt to {@link Flux} and {@link Mono}.
*
* <p>By default there are adapters for {@link CompletableFuture}, RxJava 1, and
* also for a any Reactive Streams {@link Publisher}. Additional adapters can be
* registered via {@link #registerFluxAdapter) and {@link #registerMonoAdapter}.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public class ReactiveAdapterRegistry {
private static final boolean rxJava1Present =
ClassUtils.isPresent("rx.Observable", ReactiveAdapterRegistry.class.getClassLoader());
private final Map<Class<?>, ReactiveAdapter> adapterMap = new LinkedHashMap<>();
/**
* Create a registry and auto-register default adapters.
*/
public ReactiveAdapterRegistry() {
// Flux and Mono ahead of Publisher...
registerMonoAdapter(Mono.class,
source -> (Mono<?>) source, source -> source, new Descriptor(false, true, false));
registerFluxAdapter(
Flux.class, source -> (Flux<?>) source, source -> source);
registerFluxAdapter(
Publisher.class, source -> Flux.from((Publisher<?>) source), source -> source);
registerMonoAdapter(CompletableFuture.class,
source -> Mono.fromFuture((CompletableFuture<?>) source),
source -> Mono.from((Publisher<?>) source).toFuture(),
new Descriptor(false, true, false)
);
if (rxJava1Present) {
new RxJava1AdapterRegistrar().register(this);
}
}
/**
* Register an adapter for adapting to and from a {@link Mono}. The provided
* functions can assume that input will never be {@code null} and also that
* any {@link Optional} wrapper is unwrapped.
*/
public void registerMonoAdapter(Class<?> adapteeType,
Function<Object, Mono<?>> toAdapter, Function<Mono<?>, Object> fromAdapter,
Descriptor descriptor) {
this.adapterMap.put(adapteeType, new MonoReactiveAdapter(toAdapter, fromAdapter, descriptor));
}
/**
* Register an adapter for adapting to and from a {@link Flux}. The provided
* functions can assume that input will never be {@code null} and also that
* any {@link Optional} wrapper is unwrapped.
*/
public void registerFluxAdapter(Class<?> adapteeType,
Function<Object, Flux<?>> toAdapter, Function<Flux<?>, Object> fromAdapter) {
this.adapterMap.put(adapteeType, new FluxReactiveAdapter(toAdapter, fromAdapter));
}
/**
* Get the adapter for the given adaptee type to adapt from.
*/
public ReactiveAdapter getAdapterFrom(Class<?> adapteeType) {
return getAdapterFrom(adapteeType, null);
}
/**
* Get the adapter for the given adaptee type to adapt from.
* If the instance is not {@code null} its actual type is used to check.
*/
public ReactiveAdapter getAdapterFrom(Class<?> adapteeType, Object adaptee) {
Class<?> actualType = getActualType(adapteeType, adaptee);
return getAdapterInternal(supportedType -> supportedType.isAssignableFrom(actualType));
}
/**
* Get the adapter for the given adaptee type to adapt to.
*/
public ReactiveAdapter getAdapterTo(Class<?> adapteeType) {
return getAdapterTo(adapteeType, null);
}
/**
* Get the adapter for the given adaptee type to adapt to.
* If the instance is not {@code null} its actual type is used to check.
*/
public ReactiveAdapter getAdapterTo(Class<?> adapteeType, Object adaptee) {
Class<?> actualType = getActualType(adapteeType, adaptee);
return getAdapterInternal(supportedType -> supportedType.equals(actualType));
}
private static Class<?> getActualType(Class<?> adapteeType, Object adaptee) {
adaptee = unwrapOptional(adaptee);
return (adaptee != null ? adaptee.getClass() : adapteeType);
}
private static Object unwrapOptional(Object value) {
if (value != null && value instanceof Optional) {
value = ((Optional<?>) value).orElse(null);
}
return value;
}
private ReactiveAdapter getAdapterInternal(Predicate<Class<?>> adapteeTypePredicate) {
return this.adapterMap.keySet().stream()
.filter(adapteeTypePredicate)
.map(this.adapterMap::get)
.findFirst()
.orElse(null);
}
@SuppressWarnings("unchecked")
private static class MonoReactiveAdapter implements ReactiveAdapter {
private final Function<Object, Mono<?>> toAdapter;
private final Function<Mono<?>, Object> fromAdapter;
private final Descriptor descriptor;
MonoReactiveAdapter(Function<Object, Mono<?>> to, Function<Mono<?>, Object> from, Descriptor descriptor) {
this.toAdapter = to;
this.fromAdapter = from;
this.descriptor = descriptor;
}
@Override
public Descriptor getDescriptor() {
return this.descriptor;
}
@Override
public <T> Mono<T> toMono(Object source) {
source = unwrapOptional(source);
if (source == null) {
return Mono.empty();
}
return (Mono<T>) this.toAdapter.apply(source);
}
@Override
public <T> Flux<T> toFlux(Object source) {
source = unwrapOptional(source);
if (source == null) {
return Flux.empty();
}
return (Flux<T>) this.toMono(source).flux();
}
@Override
public <T> Publisher<T> toPublisher(Object source) {
return toMono(source);
}
@Override
public Object fromPublisher(Publisher<?> source) {
return (source != null ? this.fromAdapter.apply((Mono<?>) source) : null);
}
}
@SuppressWarnings("unchecked")
private static class FluxReactiveAdapter implements ReactiveAdapter {
private final Function<Object, Flux<?>> toAdapter;
private final Function<Flux<?>, Object> fromAdapter;
private final Descriptor descriptor = new Descriptor(true, true, false);
FluxReactiveAdapter(Function<Object, Flux<?>> to, Function<Flux<?>, Object> from) {
this.toAdapter = to;
this.fromAdapter = from;
}
@Override
public Descriptor getDescriptor() {
return this.descriptor;
}
@Override
public <T> Mono<T> toMono(Object source) {
source = unwrapOptional(source);
if (source == null) {
return Mono.empty();
}
return (Mono<T>) this.toAdapter.apply(source).next();
}
@Override
public <T> Flux<T> toFlux(Object source) {
source = unwrapOptional(source);
if (source == null) {
return Flux.empty();
}
return (Flux<T>) this.toAdapter.apply(source);
}
@Override
public <T> Publisher<T> toPublisher(Object source) {
return toFlux(source);
}
@Override
public Object fromPublisher(Publisher<?> source) {
return (source != null ? this.fromAdapter.apply((Flux<?>) source) : null);
}
}
private static class RxJava1AdapterRegistrar {
public void register(ReactiveAdapterRegistry registry) {
registry.registerFluxAdapter(Observable.class,
source -> RxJava1Adapter.observableToFlux((Observable<?>) source),
RxJava1Adapter::publisherToObservable
);
registry.registerMonoAdapter(Single.class,
source -> RxJava1Adapter.singleToMono((Single<?>) source),
RxJava1Adapter::publisherToSingle,
new Descriptor(false, false, false)
);
registry.registerMonoAdapter(Completable.class,
source -> RxJava1Adapter.completableToMono((Completable) source),
RxJava1Adapter::publisherToCompletable,
new Descriptor(false, true, true)
);
}
}
}

View File

@ -1,62 +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 java.util.concurrent.CompletableFuture;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
/**
* Converter to adapt {@link CompletableFuture} to Reactive Streams and
* Reactor {@link Mono}.
*
* @author Sebastien Deleuze
* @since 5.0
*/
public class MonoToCompletableFutureConverter implements GenericConverter {
@Override
public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() {
Set<GenericConverter.ConvertiblePair> pairs = new LinkedHashSet<>(2);
pairs.add(new GenericConverter.ConvertiblePair(Mono.class, CompletableFuture.class));
pairs.add(new GenericConverter.ConvertiblePair(CompletableFuture.class, Mono.class));
return pairs;
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
else if (CompletableFuture.class.isAssignableFrom(sourceType.getType())) {
return Mono.fromFuture((CompletableFuture<?>) source);
}
else if (CompletableFuture.class.isAssignableFrom(targetType.getType())) {
return Mono.from((Publisher<?>) source).toFuture();
}
return null;
}
}

View File

@ -1,81 +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.adapter.RxJava1Adapter;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import rx.Completable;
import rx.Observable;
import rx.Single;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
/**
* Converter to adapt RxJava1 {@link Observable}, {@link Single}, and
* {@link Completable} to Reactive Streams and Reactor types.
*
* @author Stephane Maldini
* @author Sebastien Deleuze
* @since 5.0
*/
public final class ReactorToRxJava1Converter implements GenericConverter {
@Override
public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() {
Set<GenericConverter.ConvertiblePair> pairs = new LinkedHashSet<>(6);
pairs.add(new GenericConverter.ConvertiblePair(Flux.class, Observable.class));
pairs.add(new GenericConverter.ConvertiblePair(Observable.class, Flux.class));
pairs.add(new GenericConverter.ConvertiblePair(Mono.class, Single.class));
pairs.add(new GenericConverter.ConvertiblePair(Single.class, Mono.class));
pairs.add(new GenericConverter.ConvertiblePair(Mono.class, Completable.class));
pairs.add(new GenericConverter.ConvertiblePair(Completable.class, Mono.class));
return pairs;
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
if (Observable.class.isAssignableFrom(sourceType.getType())) {
return RxJava1Adapter.observableToFlux((Observable<?>) source);
}
else if (Observable.class.isAssignableFrom(targetType.getType())) {
return RxJava1Adapter.publisherToObservable((Publisher<?>) source);
}
else if (Single.class.isAssignableFrom(sourceType.getType())) {
return RxJava1Adapter.singleToMono((Single<?>) source);
}
else if (Single.class.isAssignableFrom(targetType.getType())) {
return RxJava1Adapter.publisherToSingle((Publisher<?>) source);
}
else if (Completable.class.isAssignableFrom(sourceType.getType())) {
return RxJava1Adapter.completableToMono((Completable) source);
}
else if (Completable.class.isAssignableFrom(targetType.getType())) {
return RxJava1Adapter.publisherToCompletable((Publisher<?>) source);
}
return null;
}
}

View File

@ -1,56 +0,0 @@
/*
* 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.core.convert.support;
import java.util.concurrent.CompletableFuture;
import org.junit.Before;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link ReactorToRxJava1Converter}.
* @author Rossen Stoyanchev
*/
public class MonoToCompletableFutureConverterTests {
private GenericConversionService conversionService;
@Before
public void setUp() throws Exception {
this.conversionService = new GenericConversionService();
this.conversionService.addConverter(new MonoToCompletableFutureConverter());
}
@Test
public void canConvert() throws Exception {
assertTrue(this.conversionService.canConvert(Mono.class, CompletableFuture.class));
assertTrue(this.conversionService.canConvert(CompletableFuture.class, Mono.class));
assertFalse(this.conversionService.canConvert(Flux.class, CompletableFuture.class));
assertFalse(this.conversionService.canConvert(CompletableFuture.class, Flux.class));
assertFalse(this.conversionService.canConvert(Publisher.class, CompletableFuture.class));
assertFalse(this.conversionService.canConvert(CompletableFuture.class, Publisher.class));
}
}

View File

@ -0,0 +1,80 @@
/*
* 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.core.convert.support;
import java.util.concurrent.CompletableFuture;
import org.junit.Before;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import rx.Completable;
import rx.Observable;
import rx.Single;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link ReactiveAdapterRegistry}.
* @author Rossen Stoyanchev
*/
public class ReactiveAdapterRegistryTests {
private ReactiveAdapterRegistry adapterRegistry;
@Before
public void setUp() throws Exception {
this.adapterRegistry = new ReactiveAdapterRegistry();
}
@Test
public void getDefaultAdapters() throws Exception {
testMonoAdapter(Mono.class);
testFluxAdapter(Flux.class);
testFluxAdapter(Publisher.class);
testMonoAdapter(CompletableFuture.class);
testFluxAdapter(Observable.class);
testMonoAdapter(Single.class);
testMonoAdapter(Completable.class);
}
private void testFluxAdapter(Class<?> adapteeType) {
ReactiveAdapter adapter = this.adapterRegistry.getAdapterFrom(adapteeType);
assertNotNull(adapter);
assertTrue(adapter.getDescriptor().isMultiValue());
adapter = this.adapterRegistry.getAdapterTo(adapteeType);
assertNotNull(adapter);
assertTrue(adapter.getDescriptor().isMultiValue());
}
private void testMonoAdapter(Class<?> adapteeType) {
ReactiveAdapter adapter = this.adapterRegistry.getAdapterFrom(adapteeType);
assertNotNull(adapter);
assertFalse(adapter.getDescriptor().isMultiValue());
adapter = this.adapterRegistry.getAdapterTo(adapteeType);
assertNotNull(adapter);
assertFalse(adapter.getDescriptor().isMultiValue());
}
}

View File

@ -1,69 +0,0 @@
/*
* 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.core.convert.support;
import org.junit.Before;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import rx.Completable;
import rx.Observable;
import rx.Single;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link ReactorToRxJava1Converter}.
* @author Rossen Stoyanchev
*/
public class ReactorToRxJava1ConverterTests {
private GenericConversionService conversionService;
@Before
public void setUp() throws Exception {
this.conversionService = new GenericConversionService();
this.conversionService.addConverter(new ReactorToRxJava1Converter());
}
@Test
public void canConvert() throws Exception {
assertTrue(this.conversionService.canConvert(Flux.class, Observable.class));
assertTrue(this.conversionService.canConvert(Observable.class, Flux.class));
assertTrue(this.conversionService.canConvert(Mono.class, Single.class));
assertTrue(this.conversionService.canConvert(Single.class, Mono.class));
assertTrue(this.conversionService.canConvert(Mono.class, Completable.class));
assertTrue(this.conversionService.canConvert(Completable.class, Mono.class));
assertFalse(this.conversionService.canConvert(Flux.class, Single.class));
assertFalse(this.conversionService.canConvert(Single.class, Flux.class));
assertFalse(this.conversionService.canConvert(Flux.class, Completable.class));
assertFalse(this.conversionService.canConvert(Completable.class, Flux.class));
assertFalse(this.conversionService.canConvert(Mono.class, Observable.class));
assertFalse(this.conversionService.canConvert(Observable.class, Mono.class));
assertFalse(this.conversionService.canConvert(Publisher.class, Observable.class));
assertFalse(this.conversionService.canConvert(Observable.class, Publisher.class));
}
}

View File

@ -34,8 +34,6 @@ import org.springframework.core.codec.ResourceDecoder;
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;
@ -274,17 +272,8 @@ public class WebReactiveConfiguration implements ApplicationContextAware {
/**
* Override to add custom {@link Converter}s and {@link Formatter}s.
* <p>By default this method method registers:
* <ul>
* <li>{@link MonoToCompletableFutureConverter}
* <li>{@link ReactorToRxJava1Converter}
* </ul>
*/
protected void addFormatters(FormatterRegistry registry) {
registry.addConverter(new MonoToCompletableFutureConverter());
if (rxJava1Present) {
registry.addConverter(new ReactorToRxJava1Converter());
}
}
/**
@ -334,19 +323,17 @@ public class WebReactiveConfiguration implements ApplicationContextAware {
@Bean
public SimpleResultHandler simpleResultHandler() {
return new SimpleResultHandler(mvcConversionService());
return new SimpleResultHandler();
}
@Bean
public ResponseEntityResultHandler responseEntityResultHandler() {
return new ResponseEntityResultHandler(getMessageWriters(), mvcConversionService(),
mvcContentTypeResolver());
return new ResponseEntityResultHandler(getMessageWriters(), mvcContentTypeResolver());
}
@Bean
public ResponseBodyResultHandler responseBodyResultHandler() {
return new ResponseBodyResultHandler(getMessageWriters(), mvcConversionService(),
mvcContentTypeResolver());
return new ResponseBodyResultHandler(getMessageWriters(), mvcContentTypeResolver());
}
/**
@ -405,7 +392,7 @@ public class WebReactiveConfiguration implements ApplicationContextAware {
ViewResolverRegistry registry = new ViewResolverRegistry(getApplicationContext());
configureViewResolvers(registry);
List<ViewResolver> resolvers = registry.getViewResolvers();
ViewResolutionResultHandler handler = new ViewResolutionResultHandler(resolvers, mvcConversionService());
ViewResolutionResultHandler handler = new ViewResolutionResultHandler(resolvers, mvcContentTypeResolver());
handler.setDefaultViews(registry.getDefaultViews());
handler.setOrder(registry.getOrder());
return handler;

View File

@ -24,7 +24,7 @@ import java.util.Optional;
import java.util.Set;
import org.springframework.core.Ordered;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.reactive.HandlerMapping;
@ -43,28 +43,32 @@ 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 final ReactiveAdapterRegistry adapterRegistry;
private int order = LOWEST_PRECEDENCE;
protected ContentNegotiatingResultHandlerSupport(ConversionService conversionService,
RequestedContentTypeResolver contentTypeResolver) {
protected ContentNegotiatingResultHandlerSupport(RequestedContentTypeResolver contentTypeResolver) {
this(contentTypeResolver, new ReactiveAdapterRegistry());
}
protected ContentNegotiatingResultHandlerSupport(RequestedContentTypeResolver contentTypeResolver,
ReactiveAdapterRegistry adapterRegistry) {
Assert.notNull(conversionService, "'conversionService' is required.");
Assert.notNull(contentTypeResolver, "'contentTypeResolver' is required.");
this.conversionService = conversionService;
Assert.notNull(adapterRegistry, "'adapterRegistry' is required.");
this.contentTypeResolver = contentTypeResolver;
this.adapterRegistry = adapterRegistry;
}
/**
* Return the configured {@link ConversionService}.
* Return the configured {@link ReactiveAdapterRegistry}.
*/
public ConversionService getConversionService() {
return this.conversionService;
public ReactiveAdapterRegistry getReactiveAdapterRegistry() {
return this.adapterRegistry;
}
/**

View File

@ -18,14 +18,12 @@ 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.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
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;
@ -45,27 +43,26 @@ 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 ReactiveAdapterRegistry adapterRegistry;
private int order = Ordered.LOWEST_PRECEDENCE;
public SimpleResultHandler(ConversionService conversionService) {
Assert.notNull(conversionService, "'conversionService' is required.");
this.conversionService = conversionService;
public SimpleResultHandler() {
this.adapterRegistry = new ReactiveAdapterRegistry();
}
public SimpleResultHandler(ReactiveAdapterRegistry adapterRegistry) {
Assert.notNull(adapterRegistry, "'adapterRegistry' is required.");
this.adapterRegistry = adapterRegistry;
}
/**
* Return the configured {@link ConversionService}.
* Return the configured {@link ReactiveAdapterRegistry}.
*/
public ConversionService getConversionService() {
return this.conversionService;
public ReactiveAdapterRegistry getAdapterRegistry() {
return this.adapterRegistry;
}
/**
@ -88,37 +85,28 @@ public class SimpleResultHandler implements Ordered, HandlerResultHandler {
@Override
public boolean supports(HandlerResult result) {
ResolvableType type = result.getReturnType();
if (Void.TYPE.equals(type.getRawClass())) {
Class<?> rawClass = type.getRawClass();
if (Void.TYPE.equals(rawClass)) {
return true;
}
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();
ReactiveAdapter adapter = getAdapterRegistry().getAdapterFrom(rawClass, result.getReturnValue());
if (adapter != null) {
Class<?> clazz = type.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<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
Optional<Object> optional = result.getReturnValue();
if (!optional.isPresent()) {
Optional<Object> optionalValue = result.getReturnValue();
if (!optionalValue.isPresent()) {
return Mono.empty();
}
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<Void>) getConversionService().convert(value, source, MONO_TYPE)) :
((Flux<Void>) getConversionService().convert(value, source, FLUX_TYPE)).single();
Class<?> returnType = result.getReturnType().getRawClass();
ReactiveAdapter adapter = getAdapterRegistry().getAdapterFrom(returnType, optionalValue);
return adapter.toMono(optionalValue);
}
}

View File

@ -25,10 +25,10 @@ import reactor.core.publisher.Mono;
import org.springframework.core.Conventions;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
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.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
@ -57,34 +57,39 @@ import org.springframework.web.server.UnsupportedMediaTypeStatusException;
*/
public abstract class AbstractMessageReaderArgumentResolver {
private static final TypeDescriptor MONO_TYPE = TypeDescriptor.valueOf(Mono.class);
private static final TypeDescriptor FLUX_TYPE = TypeDescriptor.valueOf(Flux.class);
private final List<HttpMessageReader<?>> messageReaders;
private final ConversionService conversionService;
private final Validator validator;
private final ReactiveAdapterRegistry adapterRegistry;
private final List<MediaType> supportedMediaTypes;
/**
* Constructor with message converters and a ConversionService.
* @param messageReaders readers to convert from the request body
* @param service for converting to other reactive types from Flux and Mono
* Constructor with {@link HttpMessageReader}'s and a {@link Validator}.
* @param readers readers to convert from the request body
* @param validator validator to validate decoded objects with
*/
protected AbstractMessageReaderArgumentResolver(List<HttpMessageReader<?>> messageReaders,
ConversionService service, Validator validator) {
protected AbstractMessageReaderArgumentResolver(List<HttpMessageReader<?>> readers, Validator validator) {
Assert.notEmpty(messageReaders, "At least one message reader is required.");
Assert.notNull(service, "'conversionService' is required.");
this(readers, validator, new ReactiveAdapterRegistry());
}
/**
* Constructor that also accepts a {@link ReactiveAdapterRegistry}.
* @param messageReaders readers to convert from the request body
* @param validator validator to validate decoded objects with
* @param adapterRegistry for adapting to other reactive types from Flux and Mono
*/
protected AbstractMessageReaderArgumentResolver(List<HttpMessageReader<?>> messageReaders,
Validator validator, ReactiveAdapterRegistry adapterRegistry) {
Assert.notEmpty(messageReaders, "At least one HttpMessageReader is required.");
Assert.notNull(adapterRegistry, "'adapterRegistry' is required");
this.messageReaders = messageReaders;
this.conversionService = service;
this.validator = validator;
this.adapterRegistry = adapterRegistry;
this.supportedMediaTypes = messageReaders.stream()
.flatMap(converter -> converter.getReadableMediaTypes().stream())
.collect(Collectors.toList());
@ -99,22 +104,21 @@ public abstract class AbstractMessageReaderArgumentResolver {
}
/**
* Return the configured {@link ConversionService}.
* Return the configured {@link ReactiveAdapterRegistry}.
*/
public ConversionService getConversionService() {
return this.conversionService;
public ReactiveAdapterRegistry getReactiveAdapterRegistry() {
return this.adapterRegistry;
}
protected Mono<Object> readBody(MethodParameter bodyParameter, boolean isBodyRequired,
ServerWebExchange exchange) {
TypeDescriptor typeDescriptor = new TypeDescriptor(bodyParameter);
boolean convertFromMono = getConversionService().canConvert(MONO_TYPE, typeDescriptor);
boolean convertFromFlux = getConversionService().canConvert(FLUX_TYPE, typeDescriptor);
Class<?> bodyType = ResolvableType.forMethodParameter(bodyParameter).resolve();
ReactiveAdapter adapter = getReactiveAdapterRegistry().getAdapterTo(bodyType);
ResolvableType elementType = ResolvableType.forMethodParameter(bodyParameter);
if (convertFromMono || convertFromFlux) {
if (adapter != null) {
elementType = elementType.getGeneric(0);
}
@ -126,28 +130,28 @@ public abstract class AbstractMessageReaderArgumentResolver {
for (HttpMessageReader<?> reader : getMessageReaders()) {
if (reader.canRead(elementType, mediaType)) {
if (convertFromFlux) {
if (adapter != null && adapter.getDescriptor().isMultiValue()) {
Flux<?> flux = reader.read(elementType, request)
.onErrorResumeWith(ex -> Flux.error(getReadError(ex, bodyParameter)));
if (checkRequired(bodyParameter, isBodyRequired)) {
if (checkRequired(adapter, isBodyRequired)) {
flux = flux.switchIfEmpty(Flux.error(getRequiredBodyError(bodyParameter)));
}
if (this.validator != null) {
flux = flux.map(applyValidationIfApplicable(bodyParameter));
}
return Mono.just(getConversionService().convert(flux, FLUX_TYPE, typeDescriptor));
return Mono.just(adapter.fromPublisher(flux));
}
else {
Mono<?> mono = reader.readMono(elementType, request)
.otherwise(ex -> Mono.error(getReadError(ex, bodyParameter)));
if (checkRequired(bodyParameter, isBodyRequired)) {
if (checkRequired(adapter, isBodyRequired)) {
mono = mono.otherwiseIfEmpty(Mono.error(getRequiredBodyError(bodyParameter)));
}
if (this.validator != null) {
mono = mono.map(applyValidationIfApplicable(bodyParameter));
}
if (convertFromMono) {
return Mono.just(getConversionService().convert(mono, MONO_TYPE, typeDescriptor));
if (adapter != null) {
return Mono.just(adapter.fromPublisher(mono));
}
else {
return Mono.from(mono);
@ -159,11 +163,8 @@ public abstract class AbstractMessageReaderArgumentResolver {
return Mono.error(new UnsupportedMediaTypeStatusException(mediaType, this.supportedMediaTypes));
}
protected boolean checkRequired(MethodParameter bodyParameter, boolean isBodyRequired) {
if ("rx.Single".equals(bodyParameter.getNestedParameterType().getName())) {
return true;
}
return isBodyRequired;
protected boolean checkRequired(ReactiveAdapter adapter, boolean isBodyRequired) {
return adapter != null && !adapter.getDescriptor().supportsEmpty() || isBodyRequired;
}
protected ServerWebInputException getReadError(Throwable ex, MethodParameter parameter) {

View File

@ -19,13 +19,12 @@ import java.util.List;
import java.util.stream.Collectors;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
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.HttpMessageWriter;
import org.springframework.http.server.reactive.ServerHttpResponse;
@ -44,31 +43,42 @@ import org.springframework.web.server.ServerWebExchange;
*/
public abstract class AbstractMessageWriterResultHandler 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<HttpMessageWriter<?>> messageWriters;
/**
* Constructor with message converters, a {@code ConversionService}, and a
* Constructor with {@link HttpMessageWriter}s and a
* {@code RequestedContentTypeResolver}.
*
* @param messageWriters for serializing Objects to the response body stream
* @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
*/
protected AbstractMessageWriterResultHandler(List<HttpMessageWriter<?>> messageWriters,
ConversionService conversionService, RequestedContentTypeResolver contentTypeResolver) {
RequestedContentTypeResolver contentTypeResolver) {
super(conversionService, contentTypeResolver);
super(contentTypeResolver);
Assert.notEmpty(messageWriters, "At least one message writer is required.");
this.messageWriters = messageWriters;
}
/**
* Constructor with an additional {@link ReactiveAdapterRegistry}.
*
* @param messageWriters for serializing Objects to the response body stream
* @param contentTypeResolver for resolving the requested content type
* @param adapterRegistry for adapting other reactive types (e.g. rx.Observable,
* rx.Single, etc.) to Flux or Mono
*/
protected AbstractMessageWriterResultHandler(List<HttpMessageWriter<?>> messageWriters,
RequestedContentTypeResolver contentTypeResolver,
ReactiveAdapterRegistry adapterRegistry) {
super(contentTypeResolver, adapterRegistry);
Assert.notEmpty(messageWriters, "At least one message writer is required.");
this.messageWriters = messageWriters;
}
/**
* Return the configured message converters.
*/
@ -78,31 +88,20 @@ public abstract class AbstractMessageWriterResultHandler extends ContentNegotiat
@SuppressWarnings("unchecked")
protected Mono<Void> writeBody(ServerWebExchange exchange, Object body,
ResolvableType bodyType, MethodParameter bodyTypeParameter) {
protected Mono<Void> writeBody(Object body, MethodParameter bodyType, ServerWebExchange exchange) {
Publisher<?> publisher = null;
Class<?> bodyClass = bodyType.getParameterType();
ReactiveAdapter adapter = getReactiveAdapterRegistry().getAdapterFrom(bodyClass, body);
Publisher<?> publisher;
ResolvableType elementType;
if (Publisher.class.isAssignableFrom(bodyType.getRawClass())) {
publisher = (Publisher<?>) body;
if (adapter != null) {
publisher = adapter.toPublisher(body);
elementType = ResolvableType.forMethodParameter(bodyType).getGeneric(0);
}
else {
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 (publisher != null) {
elementType = bodyType.getGeneric(0);
}
else {
elementType = bodyType;
publisher = Mono.justOrEmpty(body);
elementType = ResolvableType.forMethodParameter(bodyType);
}
if (void.class == elementType.getRawClass() || Void.class == elementType.getRawClass()) {

View File

@ -20,8 +20,8 @@ import java.util.List;
import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.RequestEntity;
@ -45,26 +45,24 @@ public class HttpEntityArgumentResolver extends AbstractMessageReaderArgumentRes
/**
* Constructor with message converters and a ConversionService.
* @param messageReaders readers for de-serializing the request body with
* @param service for converting to other reactive types from Flux and Mono
* Constructor with {@link HttpMessageReader}'s and a {@link Validator}.
* @param readers readers for de-serializing the request body with
* @param validator validator to validate decoded objects with
*/
public HttpEntityArgumentResolver(List<HttpMessageReader<?>> messageReaders,
ConversionService service) {
this(messageReaders, service, null);
public HttpEntityArgumentResolver(List<HttpMessageReader<?>> readers, Validator validator) {
super(readers, validator);
}
/**
* Constructor with message converters and a ConversionService.
* @param messageReaders readers for de-serializing the request body with
* @param service for converting to other reactive types from Flux and Mono
* Constructor that also accepts a {@link ReactiveAdapterRegistry}.
* @param readers readers for de-serializing the request body with
* @param validator validator to validate decoded objects with
* @param adapterRegistry for adapting to other reactive types from Flux and Mono
*/
public HttpEntityArgumentResolver(List<HttpMessageReader<?>> messageReaders,
ConversionService service, Validator validator) {
public HttpEntityArgumentResolver(List<HttpMessageReader<?>> readers, Validator validator,
ReactiveAdapterRegistry adapterRegistry) {
super(messageReaders, service, validator);
super(readers, validator, adapterRegistry);
}
@ -77,20 +75,9 @@ public class HttpEntityArgumentResolver extends AbstractMessageReaderArgumentRes
@Override
public Mono<Object> resolveArgument(MethodParameter param, ModelMap model, ServerWebExchange exchange) {
ResolvableType entityType;
MethodParameter bodyParameter;
if (getConversionService().canConvert(Mono.class, param.getParameterType())) {
entityType = ResolvableType.forMethodParameter(param).getGeneric(0);
bodyParameter = new MethodParameter(param);
bodyParameter.increaseNestingLevel();
bodyParameter.increaseNestingLevel();
}
else {
entityType = ResolvableType.forMethodParameter(param);
bodyParameter = new MethodParameter(param);
bodyParameter.increaseNestingLevel();
}
ResolvableType entityType = ResolvableType.forMethodParameter(param);
MethodParameter bodyParameter = new MethodParameter(param);
bodyParameter.increaseNestingLevel();
return readBody(bodyParameter, false, exchange)
.map(body -> createHttpEntity(body, entityType, exchange))

View File

@ -21,7 +21,7 @@ import java.util.List;
import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.http.converter.reactive.HttpMessageReader;
import org.springframework.ui.ModelMap;
import org.springframework.validation.Validator;
@ -49,26 +49,24 @@ public class RequestBodyArgumentResolver extends AbstractMessageReaderArgumentRe
/**
* Constructor with message converters and a ConversionService.
* @param messageReaders readers for de-serializing the request body with
* @param service for converting to other reactive types from Flux and Mono
* Constructor with {@link HttpMessageReader}'s and a {@link Validator}.
* @param readers readers for de-serializing the request body with
* @param validator validator to validate decoded objects with
*/
public RequestBodyArgumentResolver(List<HttpMessageReader<?>> messageReaders,
ConversionService service) {
this(messageReaders, service, null);
public RequestBodyArgumentResolver(List<HttpMessageReader<?>> readers, Validator validator) {
super(readers, validator);
}
/**
* Constructor with message converters and a ConversionService.
* @param messageReaders readers for de-serializing the request body with
* @param service for converting to other reactive types from Flux and Mono
* Constructor that also accepts a {@link ReactiveAdapterRegistry}.
* @param readers readers for de-serializing the request body with
* @param validator validator to validate decoded objects with
* @param adapterRegistry for adapting to other reactive types from Flux and Mono
*/
public RequestBodyArgumentResolver(List<HttpMessageReader<?>> messageReaders,
ConversionService service, Validator validator) {
public RequestBodyArgumentResolver(List<HttpMessageReader<?>> readers, Validator validator,
ReactiveAdapterRegistry adapterRegistry) {
super(messageReaders, service, validator);
super(readers, validator, adapterRegistry);
}

View File

@ -31,6 +31,7 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.codec.ByteBufferDecoder;
import org.springframework.core.codec.StringDecoder;
import org.springframework.core.convert.ConversionService;
@ -66,6 +67,8 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
private final List<HttpMessageReader<?>> messageReaders = new ArrayList<>(10);
private ReactiveAdapterRegistry reactiveAdapters = new ReactiveAdapterRegistry();
private ConversionService conversionService = new DefaultFormattingConversionService();
private Validator validator;
@ -126,6 +129,14 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
return this.messageReaders;
}
public void setReactiveAdapterRegistry(ReactiveAdapterRegistry registry) {
this.reactiveAdapters = registry;
}
public ReactiveAdapterRegistry getReactiveAdapterRegistry() {
return this.reactiveAdapters;
}
/**
* Configure a ConversionService for type conversion of controller method
* arguments as well as for converting from different async types to
@ -187,13 +198,15 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
protected List<HandlerMethodArgumentResolver> initArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
// Annotation-based argument resolution
ConversionService cs = getConversionService();
ReactiveAdapterRegistry adapterRegistry = getReactiveAdapterRegistry();
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(cs, getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver(cs, getBeanFactory()));
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new RequestBodyArgumentResolver(getMessageReaders(), cs, getValidator()));
resolvers.add(new RequestBodyArgumentResolver(getMessageReaders(), getValidator(), adapterRegistry));
resolvers.add(new RequestHeaderMethodArgumentResolver(cs, getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new CookieValueMethodArgumentResolver(cs, getBeanFactory()));
@ -202,6 +215,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
resolvers.add(new RequestAttributeMethodArgumentResolver(cs , getBeanFactory()));
// Type-based argument resolution
resolvers.add(new HttpEntityArgumentResolver(getMessageReaders(), getValidator(), adapterRegistry));
resolvers.add(new ModelArgumentResolver());
// Custom resolvers

View File

@ -21,15 +21,14 @@ import java.util.List;
import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.http.HttpEntity;
import org.springframework.http.converter.reactive.HttpMessageWriter;
import org.springframework.web.bind.annotation.ResponseBody;
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.server.ServerWebExchange;
@ -53,42 +52,41 @@ import org.springframework.web.server.ServerWebExchange;
public class ResponseBodyResultHandler extends AbstractMessageWriterResultHandler
implements HandlerResultHandler {
/**
* Constructor with message converters and a {@code ConversionService} only
* and creating a {@link HeaderContentTypeResolver}, i.e. using Accept header
* to determine the requested content type.
*
* @param messageWriters writers for serializing to the response body stream
* @param conversionService for converting to Flux and Mono from other reactive types
*/
public ResponseBodyResultHandler(List<HttpMessageWriter<?>> messageWriters,
ConversionService conversionService) {
this(messageWriters, conversionService, new HeaderContentTypeResolver());
}
/**
* Constructor with message converters, a {@code ConversionService}, and a
* Constructor with {@link HttpMessageWriter}s and a
* {@code RequestedContentTypeResolver}.
*
* @param messageWriters writers for serializing to the response body stream
* @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<HttpMessageWriter<?>> messageWriters,
ConversionService conversionService, RequestedContentTypeResolver contentTypeResolver) {
RequestedContentTypeResolver contentTypeResolver) {
super(messageWriters, conversionService, contentTypeResolver);
this(messageWriters, contentTypeResolver, new ReactiveAdapterRegistry());
}
/**
* Constructor with an additional {@link ReactiveAdapterRegistry}.
*
* @param messageWriters writers for serializing to the response body stream
* @param contentTypeResolver for resolving the requested content type
* @param adapterRegistry for adapting other reactive types (e.g. rx.Observable,
* rx.Single, etc.) to Flux or Mono
*/
public ResponseBodyResultHandler(List<HttpMessageWriter<?>> messageWriters,
RequestedContentTypeResolver contentTypeResolver,
ReactiveAdapterRegistry adapterRegistry) {
super(messageWriters, contentTypeResolver, adapterRegistry);
setOrder(100);
}
@Override
public boolean supports(HandlerResult result) {
ResolvableType returnType = result.getReturnType();
MethodParameter parameter = result.getReturnTypeSource();
return hasResponseBodyAnnotation(parameter) && !isHttpEntityType(returnType);
return hasResponseBodyAnnotation(parameter) && !isHttpEntityType(result);
}
private boolean hasResponseBodyAnnotation(MethodParameter parameter) {
@ -97,26 +95,27 @@ public class ResponseBodyResultHandler extends AbstractMessageWriterResultHandle
parameter.getMethodAnnotation(ResponseBody.class) != null);
}
private boolean isHttpEntityType(ResolvableType returnType) {
if (HttpEntity.class.isAssignableFrom(returnType.getRawClass())) {
private boolean isHttpEntityType(HandlerResult result) {
Class<?> rawClass = result.getReturnType().getRawClass();
if (HttpEntity.class.isAssignableFrom(rawClass)) {
return true;
}
else if (getConversionService().canConvert(returnType.getRawClass(), Mono.class)) {
ResolvableType genericType = returnType.getGeneric(0);
if (HttpEntity.class.isAssignableFrom(genericType.getRawClass())) {
return true;
else {
if (getReactiveAdapterRegistry().getAdapterFrom(rawClass, result.getReturnValue()) != null) {
ResolvableType genericType = result.getReturnType().getGeneric(0);
if (HttpEntity.class.isAssignableFrom(genericType.getRawClass())) {
return true;
}
}
}
return false;
}
@Override
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
Object body = result.getReturnValue().orElse(null);
ResolvableType bodyType = result.getReturnType();
MethodParameter bodyTypeParameter = result.getReturnTypeSource();
return writeBody(exchange, body, bodyType, bodyTypeParameter);
return writeBody(body, bodyTypeParameter, exchange);
}
}

View File

@ -21,8 +21,9 @@ import java.util.Optional;
import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.RequestEntity;
@ -31,7 +32,6 @@ import org.springframework.http.converter.reactive.HttpMessageWriter;
import org.springframework.util.Assert;
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.server.ServerWebExchange;
@ -47,52 +47,55 @@ import org.springframework.web.server.ServerWebExchange;
public class ResponseEntityResultHandler extends AbstractMessageWriterResultHandler
implements HandlerResultHandler {
/**
* Constructor with message converters and a {@code ConversionService} only
* and creating a {@link HeaderContentTypeResolver}, i.e. using Accept header
* to determine the requested content type.
*
* @param messageWriters writers for serializing to the response body stream
* @param conversionService for converting to Flux and Mono from other reactive types
*/
public ResponseEntityResultHandler(List<HttpMessageWriter<?>> messageWriters,
ConversionService conversionService) {
this(messageWriters, conversionService, new HeaderContentTypeResolver());
}
/**
* Constructor with message converters, a {@code ConversionService}, and a
* Constructor with {@link HttpMessageWriter}s and a
* {@code RequestedContentTypeResolver}.
*
* @param messageWriters writers for serializing to the response body stream
* @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 ResponseEntityResultHandler(List<HttpMessageWriter<?>> messageWriters,
ConversionService conversionService, RequestedContentTypeResolver contentTypeResolver) {
RequestedContentTypeResolver contentTypeResolver) {
super(messageWriters, conversionService, contentTypeResolver);
this(messageWriters, contentTypeResolver, new ReactiveAdapterRegistry());
}
/**
* Constructor with an additional {@link ReactiveAdapterRegistry}.
*
* @param messageWriters writers for serializing to the response body stream
* @param contentTypeResolver for resolving the requested content type
* @param adapterRegistry for adapting other reactive types (e.g. rx.Observable,
* rx.Single, etc.) to Flux or Mono
*/
public ResponseEntityResultHandler(List<HttpMessageWriter<?>> messageWriters,
RequestedContentTypeResolver contentTypeResolver,
ReactiveAdapterRegistry adapterRegistry) {
super(messageWriters, contentTypeResolver, adapterRegistry);
setOrder(0);
}
@Override
public boolean supports(HandlerResult result) {
ResolvableType returnType = result.getReturnType();
Class<?> returnType = result.getReturnType().getRawClass();
if (isSupportedType(returnType)) {
return true;
}
else if (getConversionService().canConvert(returnType.getRawClass(), Mono.class)) {
ResolvableType genericType = result.getReturnType().getGeneric(0);
return isSupportedType(genericType);
else {
Optional<Object> returnValue = result.getReturnValue();
ReactiveAdapter adapter = getReactiveAdapterRegistry().getAdapterFrom(returnType, returnValue);
if (adapter != null && !adapter.getDescriptor().isMultiValue()) {
ResolvableType genericType = result.getReturnType().getGeneric(0);
return isSupportedType(genericType.getRawClass());
}
}
return false;
}
private boolean isSupportedType(ResolvableType returnType) {
Class<?> clazz = returnType.getRawClass();
private boolean isSupportedType(Class<?> clazz) {
return (HttpEntity.class.isAssignableFrom(clazz) && !RequestEntity.class.isAssignableFrom(clazz));
}
@ -101,25 +104,24 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
ResolvableType returnType = result.getReturnType();
ResolvableType bodyType;
MethodParameter bodyTypeParameter;
MethodParameter bodyType;
Mono<?> returnValueMono;
Optional<Object> optional = result.getReturnValue();
Optional<Object> optionalValue = result.getReturnValue();
if (optional.isPresent() && getConversionService().canConvert(returnType.getRawClass(), Mono.class)) {
returnValueMono = getConversionService().convert(optional.get(), Mono.class);
bodyType = returnType.getGeneric(0, 0);
bodyTypeParameter = new MethodParameter(result.getReturnTypeSource());
bodyTypeParameter.increaseNestingLevel();
bodyTypeParameter.increaseNestingLevel();
Class<?> rawClass = returnType.getRawClass();
ReactiveAdapter adapter = getReactiveAdapterRegistry().getAdapterFrom(rawClass, optionalValue);
if (adapter != null) {
returnValueMono = adapter.toMono(optionalValue);
bodyType = new MethodParameter(result.getReturnTypeSource());
bodyType.increaseNestingLevel();
bodyType.increaseNestingLevel();
}
else {
returnValueMono = Mono.justOrEmpty(optional);
bodyType = returnType.getGeneric(0);
bodyTypeParameter = new MethodParameter(result.getReturnTypeSource());
bodyTypeParameter.increaseNestingLevel();
returnValueMono = Mono.justOrEmpty(optionalValue);
bodyType = new MethodParameter(result.getReturnTypeSource());
bodyType.increaseNestingLevel();
}
return returnValueMono.then(returnValue -> {
@ -141,7 +143,7 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand
.forEach(entry -> responseHeaders.put(entry.getKey(), entry.getValue()));
}
return writeBody(exchange, httpEntity.getBody(), bodyType, bodyTypeParameter);
return writeBody(httpEntity.getBody(), bodyType, exchange);
});
}

View File

@ -31,16 +31,16 @@ import org.springframework.core.Conventions;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.convert.ConversionService;
import org.springframework.http.MediaType;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ModelAttribute;
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;
@ -84,26 +84,28 @@ public class ViewResolutionResultHandler extends ContentNegotiatingResultHandler
/**
* Constructor with {@code ViewResolver}s and a {@code ConversionService} only
* and creating a {@link HeaderContentTypeResolver}, i.e. using Accept header
* to determine the requested content type.
* Constructor with {@link ViewResolver}s and a {@link RequestedContentTypeResolver}.
* @param resolvers the resolver to use
* @param conversionService for converting other reactive types (e.g. rx.Single) to Mono
* @param contentTypeResolver for resolving the requested content type
*/
public ViewResolutionResultHandler(List<ViewResolver> resolvers, ConversionService conversionService) {
this(resolvers, conversionService, new HeaderContentTypeResolver());
public ViewResolutionResultHandler(List<ViewResolver> resolvers,
RequestedContentTypeResolver contentTypeResolver) {
this(resolvers, contentTypeResolver, new ReactiveAdapterRegistry());
}
/**
* Constructor with {@code ViewResolver}s tand a {@code ConversionService}.
* @param resolvers the resolver to use
* @param conversionService for converting other reactive types (e.g. rx.Single) to Mono
* @param contentTypeResolver for resolving the requested content type
* @param adapterRegistry for adapting from other reactive types (e.g.
* rx.Single) to Mono
*/
public ViewResolutionResultHandler(List<ViewResolver> resolvers, ConversionService conversionService,
RequestedContentTypeResolver contentTypeResolver) {
public ViewResolutionResultHandler(List<ViewResolver> resolvers,
RequestedContentTypeResolver contentTypeResolver,
ReactiveAdapterRegistry adapterRegistry) {
super(conversionService, contentTypeResolver);
super(contentTypeResolver, adapterRegistry);
this.viewResolvers.addAll(resolvers);
AnnotationAwareOrderComparator.sort(this.viewResolvers);
}
@ -143,7 +145,7 @@ public class ViewResolutionResultHandler extends ContentNegotiatingResultHandler
if (isSupportedType(clazz)) {
return true;
}
if (getConversionService().canConvert(clazz, Mono.class)) {
if (getReactiveAdapterRegistry().getAdapterFrom(clazz, result.getReturnValue()) != null) {
clazz = result.getReturnType().getGeneric(0).getRawClass();
return isSupportedType(clazz);
}
@ -168,10 +170,12 @@ public class ViewResolutionResultHandler extends ContentNegotiatingResultHandler
ResolvableType elementType;
ResolvableType returnType = result.getReturnType();
if (getConversionService().canConvert(returnType.getRawClass(), Mono.class)) {
Optional<Object> optionalValue = result.getReturnValue();
Class<?> rawClass = returnType.getRawClass();
Optional<Object> optionalValue = result.getReturnValue();
ReactiveAdapter adapter = getReactiveAdapterRegistry().getAdapterFrom(rawClass, optionalValue);
if (adapter != null) {
if (optionalValue.isPresent()) {
Mono<?> converted = getConversionService().convert(optionalValue.get(), Mono.class);
Mono<?> converted = adapter.toMono(optionalValue);
valueMono = converted.map(o -> o);
}
else {

View File

@ -30,7 +30,6 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.codec.StringEncoder;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpMethod;
@ -43,6 +42,7 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.reactive.accept.HeaderContentTypeResolver;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler;
@ -198,7 +198,7 @@ public class DispatcherHandlerErrorTests {
public ResponseBodyResultHandler resultHandler() {
return new ResponseBodyResultHandler(
Collections.singletonList(new EncoderHttpMessageWriter<>(new StringEncoder())),
new DefaultConversionService());
new HeaderContentTypeResolver());
}
@Bean

View File

@ -24,7 +24,6 @@ import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.MockServerHttpRequest;
@ -125,7 +124,7 @@ public class ContentNegotiatingResultHandlerSupportTests {
}
public TestResultHandler(RequestedContentTypeResolver contentTypeResolver) {
super(new GenericConversionService(), contentTypeResolver);
super(contentTypeResolver);
}
}

View File

@ -27,10 +27,6 @@ import rx.Observable;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.support.MonoToCompletableFutureConverter;
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;
@ -47,10 +43,7 @@ public class SimpleResultHandlerTests {
@Before
public void setUp() throws Exception {
FormattingConversionService service = new DefaultFormattingConversionService();
service.addConverter(new MonoToCompletableFutureConverter());
service.addConverter(new ReactorToRxJava1Converter());
this.resultHandler = new SimpleResultHandler(service);
this.resultHandler = new SimpleResultHandler();
}

View File

@ -28,7 +28,6 @@ import reactor.core.publisher.Mono;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
@ -147,7 +146,7 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler
@Bean
public SimpleResultHandler resultHandler() {
return new SimpleResultHandler(new DefaultConversionService());
return new SimpleResultHandler();
}
}

View File

@ -35,12 +35,8 @@ import rx.Single;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.StringDecoder;
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.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
@ -50,14 +46,20 @@ import org.springframework.http.converter.reactive.HttpMessageReader;
import org.springframework.http.server.reactive.MockServerHttpRequest;
import org.springframework.http.server.reactive.MockServerHttpResponse;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.validation.Validator;
import org.springframework.web.reactive.result.ResolvableMethod;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.MockWebSessionManager;
import static org.junit.Assert.*;
import static org.springframework.core.ResolvableType.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
/**
* Unit tests for {@link HttpEntityArgumentResolver}.When adding a test also
@ -87,12 +89,7 @@ public class HttpEntityArgumentResolverTests {
private HttpEntityArgumentResolver createResolver() {
List<HttpMessageReader<?>> readers = new ArrayList<>();
readers.add(new DecoderHttpMessageReader<>(new StringDecoder()));
FormattingConversionService service = new DefaultFormattingConversionService();
service.addConverter(new MonoToCompletableFutureConverter());
service.addConverter(new ReactorToRxJava1Converter());
return new HttpEntityArgumentResolver(readers, service);
return new HttpEntityArgumentResolver(readers, mock(Validator.class));
}

View File

@ -42,12 +42,8 @@ import rx.Single;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.Decoder;
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;
@ -66,8 +62,12 @@ import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.MockWebSessionManager;
import static org.junit.Assert.*;
import static org.springframework.core.ResolvableType.*;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.core.ResolvableType.forClass;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
/**
* Unit tests for {@link AbstractMessageReaderArgumentResolver}.
@ -275,15 +275,9 @@ public class MessageReaderArgumentResolverTests {
@SuppressWarnings("Convert2MethodRef")
private AbstractMessageReaderArgumentResolver resolver(Decoder<?>... decoders) {
List<HttpMessageReader<?>> readers = new ArrayList<>();
Arrays.asList(decoders).forEach(decoder -> readers.add(new DecoderHttpMessageReader<>(decoder)));
FormattingConversionService service = new DefaultFormattingConversionService();
service.addConverter(new MonoToCompletableFutureConverter());
service.addConverter(new ReactorToRxJava1Converter());
return new AbstractMessageReaderArgumentResolver(readers, service, new TestBeanValidator()) {};
return new AbstractMessageReaderArgumentResolver(readers, new TestBeanValidator()) {};
}
private DataBuffer dataBuffer(String body) {

View File

@ -41,9 +41,6 @@ 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.ReactorToRxJava1Converter;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
@ -64,9 +61,11 @@ import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.MockWebSessionManager;
import static org.junit.Assert.*;
import static org.springframework.http.MediaType.*;
import static org.springframework.web.reactive.HandlerMapping.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
import static org.springframework.web.reactive.HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE;
/**
* Unit tests for {@link AbstractMessageWriterResultHandler}.
@ -93,7 +92,7 @@ public class MessageWriterResultHandlerTests {
public void useDefaultContentType() throws Exception {
Resource body = new ClassPathResource("logo.png", getClass());
ResolvableType type = ResolvableType.forType(Resource.class);
this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5));
this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5));
assertEquals("image/x-png", this.response.getHeaders().getFirst("Content-Type"));
}
@ -105,7 +104,7 @@ public class MessageWriterResultHandlerTests {
String body = "foo";
ResolvableType type = ResolvableType.forType(String.class);
this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5));
this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5));
assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType());
}
@ -119,7 +118,7 @@ public class MessageWriterResultHandlerTests {
}
private void testVoidReturnType(Object body, ResolvableType type) {
this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5));
this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5));
assertNull(this.response.getHeaders().get("Content-Type"));
assertNull(this.response.getBody());
@ -131,7 +130,7 @@ public class MessageWriterResultHandlerTests {
ResolvableType type = ResolvableType.forType(OutputStream.class);
HttpMessageWriter<?> writer = new EncoderHttpMessageWriter<>(new ByteBufferEncoder());
Mono<Void> mono = createResultHandler(writer).writeBody(this.exchange, body, type, returnType(type));
Mono<Void> mono = createResultHandler(writer).writeBody(body, returnType(type), this.exchange);
TestSubscriber.subscribe(mono).assertError(IllegalStateException.class);
}
@ -140,7 +139,7 @@ public class MessageWriterResultHandlerTests {
public void jacksonTypeOfListElement() throws Exception {
List<ParentClass> body = Arrays.asList(new Foo("foo"), new Bar("bar"));
ResolvableType type = ResolvableType.forClassWithGenerics(List.class, ParentClass.class);
this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5));
this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5));
assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType());
assertResponseBody("[{\"type\":\"foo\",\"parentProperty\":\"foo\"}," +
@ -151,7 +150,7 @@ public class MessageWriterResultHandlerTests {
public void jacksonTypeWithSubType() throws Exception {
SimpleBean body = new SimpleBean(123L, "foo");
ResolvableType type = ResolvableType.forClass(Identifiable.class);
this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5));
this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5));
assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType());
assertResponseBody("{\"id\":123,\"name\":\"foo\"}");
@ -161,7 +160,7 @@ public class MessageWriterResultHandlerTests {
public void jacksonTypeWithSubTypeOfListElement() throws Exception {
List<SimpleBean> body = Arrays.asList(new SimpleBean(123L, "foo"), new SimpleBean(456L, "bar"));
ResolvableType type = ResolvableType.forClassWithGenerics(List.class, Identifiable.class);
this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5));
this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5));
assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType());
assertResponseBody("[{\"id\":123,\"name\":\"foo\"},{\"id\":456,\"name\":\"bar\"}]");
@ -185,14 +184,8 @@ public class MessageWriterResultHandlerTests {
else {
writerList = Arrays.asList(writers);
}
GenericConversionService service = new GenericConversionService();
service.addConverter(new MonoToCompletableFutureConverter());
service.addConverter(new ReactorToRxJava1Converter());
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build();
return new AbstractMessageWriterResultHandler(writerList, service, resolver) {};
return new AbstractMessageWriterResultHandler(writerList, resolver) {};
}
private void assertResponseBody(String responseBody) {

View File

@ -18,7 +18,6 @@ package org.springframework.web.reactive.result.method.annotation;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
@ -38,18 +37,15 @@ import rx.Single;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.StringDecoder;
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.converter.reactive.DecoderHttpMessageReader;
import org.springframework.http.converter.reactive.HttpMessageReader;
import org.springframework.http.server.reactive.MockServerHttpRequest;
import org.springframework.http.server.reactive.MockServerHttpResponse;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.reactive.result.ResolvableMethod;
import org.springframework.web.server.ServerWebExchange;
@ -62,6 +58,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.springframework.core.ResolvableType.forClass;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
@ -93,12 +90,7 @@ public class RequestBodyArgumentResolverTests {
private RequestBodyArgumentResolver resolver() {
List<HttpMessageReader<?>> readers = new ArrayList<>();
readers.add(new DecoderHttpMessageReader<>(new StringDecoder()));
FormattingConversionService service = new DefaultFormattingConversionService();
service.addConverter(new MonoToCompletableFutureConverter());
service.addConverter(new ReactorToRxJava1Converter());
return new RequestBodyArgumentResolver(readers, service);
return new RequestBodyArgumentResolver(readers, mock(Validator.class));
}

View File

@ -27,11 +27,6 @@ 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.MonoToCompletableFutureConverter;
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,12 +94,8 @@ public class ResponseBodyResultHandlerTests {
else {
writerList = Arrays.asList(writers);
}
FormattingConversionService service = new DefaultFormattingConversionService();
service.addConverter(new MonoToCompletableFutureConverter());
service.addConverter(new ReactorToRxJava1Converter());
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build();
return new ResponseBodyResultHandler(writerList, new DefaultConversionService(), resolver);
return new ResponseBodyResultHandler(writerList, resolver);
}
@Test

View File

@ -34,11 +34,7 @@ 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.MonoToCompletableFutureConverter;
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;
@ -59,8 +55,11 @@ import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.MockWebSessionManager;
import static org.junit.Assert.*;
import static org.springframework.core.ResolvableType.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
/**
* Unit tests for {@link ResponseEntityResultHandler}. When adding a test also
@ -100,14 +99,8 @@ public class ResponseEntityResultHandlerTests {
else {
writerList = Arrays.asList(writers);
}
FormattingConversionService service = new DefaultFormattingConversionService();
service.addConverter(new MonoToCompletableFutureConverter());
service.addConverter(new ReactorToRxJava1Converter());
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build();
return new ResponseEntityResultHandler(writerList, service, resolver);
return new ResponseEntityResultHandler(writerList, resolver);
}

View File

@ -38,13 +38,9 @@ import rx.Single;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
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;
@ -55,6 +51,8 @@ import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.accept.HeaderContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.result.ResolvableMethod;
import org.springframework.web.server.NotAcceptableStatusException;
import org.springframework.web.server.ServerWebExchange;
@ -62,9 +60,10 @@ import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.DefaultWebSessionManager;
import org.springframework.web.server.session.WebSessionManager;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.springframework.http.MediaType.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.springframework.http.MediaType.APPLICATION_JSON;
/**
* Unit tests for {@link ViewResolutionResultHandler}.
@ -247,12 +246,9 @@ public class ViewResolutionResultHandlerTests {
}
private ViewResolutionResultHandler createResultHandler(List<View> defaultViews, ViewResolver... resolvers) {
FormattingConversionService service = new DefaultFormattingConversionService();
service.addConverter(new MonoToCompletableFutureConverter());
service.addConverter(new ReactorToRxJava1Converter());
List<ViewResolver> resolverList = Arrays.asList(resolvers);
ViewResolutionResultHandler handler = new ViewResolutionResultHandler(resolverList, service);
RequestedContentTypeResolver contentTypeResolver = new HeaderContentTypeResolver();
ViewResolutionResultHandler handler = new ViewResolutionResultHandler(resolverList, contentTypeResolver);
handler.setDefaultViews(defaultViews);
return handler;
}