Support handler methods returning void, Observable<Void>, etc.
This commit is contained in:
parent
57dc8199fb
commit
91c2b7afad
|
|
@ -23,22 +23,35 @@ import reactor.Publishers;
|
|||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.http.server.ReactiveServerHttpRequest;
|
||||
import org.springframework.http.server.ReactiveServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.HandlerResult;
|
||||
import org.springframework.web.reactive.HandlerResultHandler;
|
||||
|
||||
/**
|
||||
* Supports {@link HandlerResult} with a {@code Publisher<Void>} value.
|
||||
* Supports {@link HandlerResult} with a {@code void} or {@code Publisher<Void>} value.
|
||||
* An optional {link ConversionService} can be used to support types that can be converted to
|
||||
* {@code Publisher<Void>}, like {@code Observable<Void>} or {@code CompletableFuture<Void>}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class SimpleHandlerResultHandler implements Ordered, HandlerResultHandler {
|
||||
|
||||
private static final ResolvableType PUBLISHER_VOID = ResolvableType.forClassWithGenerics(Publisher.class, Void.class);
|
||||
|
||||
private int order = Ordered.LOWEST_PRECEDENCE;
|
||||
|
||||
private ConversionService conversionService;
|
||||
|
||||
|
||||
public SimpleHandlerResultHandler() {
|
||||
}
|
||||
|
||||
public SimpleHandlerResultHandler(ConversionService conversionService) {
|
||||
Assert.notNull(conversionService, "'conversionService' is required.");
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
|
|
@ -52,14 +65,24 @@ public class SimpleHandlerResultHandler implements Ordered, HandlerResultHandler
|
|||
@Override
|
||||
public boolean supports(HandlerResult result) {
|
||||
ResolvableType type = result.getValueType();
|
||||
return type != null && PUBLISHER_VOID.isAssignableFrom(type);
|
||||
return type != null && Void.TYPE.equals(type.getRawClass()) ||
|
||||
(Void.class.isAssignableFrom(type.getGeneric(0).getRawClass()) && isConvertibleToPublisher(type));
|
||||
}
|
||||
|
||||
private boolean isConvertibleToPublisher(ResolvableType type) {
|
||||
return Publisher.class.isAssignableFrom(type.getRawClass()) ||
|
||||
((this.conversionService != null) && this.conversionService.canConvert(type.getRawClass(), Publisher.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Publisher<Void> handleResult(ReactiveServerHttpRequest request,
|
||||
ReactiveServerHttpResponse response, HandlerResult result) {
|
||||
|
||||
Publisher<Void> completion = Publishers.completable((Publisher<?>)result.getValue());
|
||||
Object value = result.getValue();
|
||||
if (Void.TYPE.equals(result.getValueType().getRawClass())) {
|
||||
return response.writeHeaders();
|
||||
}
|
||||
Publisher<Void> completion = (value instanceof Publisher ? (Publisher<Void>)value : this.conversionService.convert(value, Publisher.class));
|
||||
return Publishers.concat(Publishers.from(Arrays.asList(completion, response.writeHeaders())));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,12 +16,20 @@
|
|||
|
||||
package org.springframework.web.reactive.handler;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import org.junit.Test;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.rx.Stream;
|
||||
import rx.Observable;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.core.convert.support.ReactiveStreamsToCompletableFutureConverter;
|
||||
import org.springframework.core.convert.support.ReactiveStreamsToReactorConverter;
|
||||
import org.springframework.core.convert.support.ReactiveStreamsToRxJava1Converter;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.reactive.HandlerResult;
|
||||
|
||||
|
|
@ -38,7 +46,7 @@ public class SimpleHandlerResultHandlerTests {
|
|||
|
||||
HandlerMethod hm = new HandlerMethod(controller, TestController.class.getMethod("voidReturnValue"));
|
||||
ResolvableType type = ResolvableType.forMethodParameter(hm.getReturnType());
|
||||
assertFalse(resultHandler.supports(new HandlerResult(hm, null, type)));
|
||||
assertTrue(resultHandler.supports(new HandlerResult(hm, null, type)));
|
||||
|
||||
hm = new HandlerMethod(controller, TestController.class.getMethod("publisherString"));
|
||||
type = ResolvableType.forMethodParameter(hm.getReturnType());
|
||||
|
|
@ -47,14 +55,61 @@ public class SimpleHandlerResultHandlerTests {
|
|||
hm = new HandlerMethod(controller, TestController.class.getMethod("publisherVoid"));
|
||||
type = ResolvableType.forMethodParameter(hm.getReturnType());
|
||||
assertTrue(resultHandler.supports(new HandlerResult(hm, null, type)));
|
||||
|
||||
hm = new HandlerMethod(controller, TestController.class.getMethod("streamVoid"));
|
||||
type = ResolvableType.forMethodParameter(hm.getReturnType());
|
||||
// Reactor Stream is a Publisher
|
||||
assertTrue(resultHandler.supports(new HandlerResult(hm, null, type)));
|
||||
|
||||
hm = new HandlerMethod(controller, TestController.class.getMethod("observableVoid"));
|
||||
type = ResolvableType.forMethodParameter(hm.getReturnType());
|
||||
assertFalse(resultHandler.supports(new HandlerResult(hm, null, type)));
|
||||
|
||||
hm = new HandlerMethod(controller, TestController.class.getMethod("completableFutureVoid"));
|
||||
type = ResolvableType.forMethodParameter(hm.getReturnType());
|
||||
assertFalse(resultHandler.supports(new HandlerResult(hm, null, type)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsWithConversionService() throws NoSuchMethodException {
|
||||
|
||||
GenericConversionService conversionService = new GenericConversionService();
|
||||
conversionService.addConverter(new ReactiveStreamsToCompletableFutureConverter());
|
||||
conversionService.addConverter(new ReactiveStreamsToReactorConverter());
|
||||
conversionService.addConverter(new ReactiveStreamsToRxJava1Converter());
|
||||
SimpleHandlerResultHandler resultHandler = new SimpleHandlerResultHandler(conversionService);
|
||||
TestController controller = new TestController();
|
||||
|
||||
HandlerMethod hm = new HandlerMethod(controller, TestController.class.getMethod("voidReturnValue"));
|
||||
ResolvableType type = ResolvableType.forMethodParameter(hm.getReturnType());
|
||||
assertTrue(resultHandler.supports(new HandlerResult(hm, null, type)));
|
||||
|
||||
hm = new HandlerMethod(controller, TestController.class.getMethod("publisherString"));
|
||||
type = ResolvableType.forMethodParameter(hm.getReturnType());
|
||||
assertFalse(resultHandler.supports(new HandlerResult(hm, null, type)));
|
||||
|
||||
hm = new HandlerMethod(controller, TestController.class.getMethod("publisherVoid"));
|
||||
type = ResolvableType.forMethodParameter(hm.getReturnType());
|
||||
assertTrue(resultHandler.supports(new HandlerResult(hm, null, type)));
|
||||
|
||||
hm = new HandlerMethod(controller, TestController.class.getMethod("streamVoid"));
|
||||
type = ResolvableType.forMethodParameter(hm.getReturnType());
|
||||
assertTrue(resultHandler.supports(new HandlerResult(hm, null, type)));
|
||||
|
||||
hm = new HandlerMethod(controller, TestController.class.getMethod("observableVoid"));
|
||||
type = ResolvableType.forMethodParameter(hm.getReturnType());
|
||||
assertTrue(resultHandler.supports(new HandlerResult(hm, null, type)));
|
||||
|
||||
hm = new HandlerMethod(controller, TestController.class.getMethod("completableFutureVoid"));
|
||||
type = ResolvableType.forMethodParameter(hm.getReturnType());
|
||||
assertTrue(resultHandler.supports(new HandlerResult(hm, null, type)));
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class TestController {
|
||||
|
||||
public Publisher<String> voidReturnValue() {
|
||||
return null;
|
||||
public void voidReturnValue() {
|
||||
}
|
||||
|
||||
public Publisher<String> publisherString() {
|
||||
|
|
@ -64,6 +119,18 @@ public class SimpleHandlerResultHandlerTests {
|
|||
public Publisher<Void> publisherVoid() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Stream<Void> streamVoid() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Observable<Void> observableVoid() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> completableFutureVoid() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -197,16 +197,18 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati
|
|||
}
|
||||
|
||||
@Test
|
||||
public void create() throws Exception {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
URI url = new URI("http://localhost:" + this.port + "/create");
|
||||
RequestEntity<List<Person>> request = RequestEntity.post(url)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(Arrays.asList(new Person("Robert"), new Person("Marie")));
|
||||
ResponseEntity<Void> response = restTemplate.exchange(request, Void.class);
|
||||
public void publisherCreate() throws Exception {
|
||||
create("http://localhost:" + this.port + "/publisher-create");
|
||||
}
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertEquals(2, this.wac.getBean(TestController.class).persons.size());
|
||||
@Test
|
||||
public void streamCreate() throws Exception {
|
||||
create("http://localhost:" + this.port + "/stream-create");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void observableCreate() throws Exception {
|
||||
create("http://localhost:" + this.port + "/observable-create");
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -259,6 +261,18 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati
|
|||
assertEquals("MARIE", results.get(1).getName());
|
||||
}
|
||||
|
||||
private void create(String requestUrl) throws Exception {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
URI url = new URI(requestUrl);
|
||||
RequestEntity<List<Person>> request = RequestEntity.post(url)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(Arrays.asList(new Person("Robert"), new Person("Marie")));
|
||||
ResponseEntity<Void> response = restTemplate.exchange(request, Void.class);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertEquals(2, this.wac.getBean(TestController.class).persons.size());
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@SuppressWarnings("unused")
|
||||
|
|
@ -295,7 +309,7 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati
|
|||
|
||||
@Bean
|
||||
public SimpleHandlerResultHandler simpleHandlerResultHandler() {
|
||||
return new SimpleHandlerResultHandler();
|
||||
return new SimpleHandlerResultHandler(conversionService());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -448,11 +462,21 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati
|
|||
});
|
||||
}
|
||||
|
||||
@RequestMapping("/create")
|
||||
public Publisher<Void> create(@RequestBody Stream<Person> personStream) {
|
||||
@RequestMapping("/publisher-create")
|
||||
public Publisher<Void> publisherCreate(@RequestBody Publisher<Person> personStream) {
|
||||
return Streams.wrap(personStream).toList().onSuccess(persons::addAll).after();
|
||||
}
|
||||
|
||||
@RequestMapping("/stream-create")
|
||||
public Promise<Void> streamCreate(@RequestBody Stream<Person> personStream) {
|
||||
return personStream.toList().onSuccess(persons::addAll).after();
|
||||
}
|
||||
|
||||
@RequestMapping("/observable-create")
|
||||
public Observable<Void> observableCreate(@RequestBody Observable<Person> personStream) {
|
||||
return personStream.toList().doOnNext(p -> persons.addAll(p)).flatMap(document -> Observable.empty());
|
||||
}
|
||||
|
||||
//TODO add mixed and T request mappings tests
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue