Consistently support CompletionStage next to CompletableFuture

Issue: SPR-15258
This commit is contained in:
Juergen Hoeller 2017-02-15 23:16:11 +01:00
parent dbf5b1e573
commit 50d93d3794
6 changed files with 49 additions and 22 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2016 the original author or authors. * Copyright 2002-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,15 +17,18 @@
package org.springframework.util.concurrent; package org.springframework.util.concurrent;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.function.BiFunction; import java.util.function.BiFunction;
/** /**
* Adapts a {@link CompletableFuture} into a {@link ListenableFuture}. * Adapts a {@link CompletableFuture} or {@link CompletionStage} into a
* Spring {@link ListenableFuture}.
* *
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Juergen Hoeller
* @since 4.2 * @since 4.2
*/ */
public class CompletableToListenableFutureAdapter<T> implements ListenableFuture<T> { public class CompletableToListenableFutureAdapter<T> implements ListenableFuture<T> {
@ -35,6 +38,17 @@ public class CompletableToListenableFutureAdapter<T> implements ListenableFuture
private final ListenableFutureCallbackRegistry<T> callbacks = new ListenableFutureCallbackRegistry<>(); private final ListenableFutureCallbackRegistry<T> callbacks = new ListenableFutureCallbackRegistry<>();
/**
* Create a new adapter for the given {@link CompletionStage}.
* @since 4.3.7
*/
public CompletableToListenableFutureAdapter(CompletionStage<T> completionStage) {
this(completionStage.toCompletableFuture());
}
/**
* Create a new adapter for the given {@link CompletableFuture}.
*/
public CompletableToListenableFutureAdapter(CompletableFuture<T> completableFuture) { public CompletableToListenableFutureAdapter(CompletableFuture<T> completableFuture) {
this.completableFuture = completableFuture; this.completableFuture = completableFuture;
this.completableFuture.handle(new BiFunction<T, Throwable, Object>() { this.completableFuture.handle(new BiFunction<T, Throwable, Object>() {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2015 the original author or authors. * Copyright 2002-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -66,8 +66,12 @@ import org.springframework.messaging.Message;
* HTTP handshake that initiates WebSocket sessions.</li> * HTTP handshake that initiates WebSocket sessions.</li>
* </ul> * </ul>
* *
* <p>By default the return value is wrapped as a message and sent to the destination * <p>A return value will get wrapped as a message and sent to a default response
* specified with an {@link SendTo @SendTo} method-level annotation. * destination or to a custom destination specified with an {@link SendTo @SendTo}
* method-level annotation. Such a response may also be provided asynchronously
* via a {@link org.springframework.util.concurrent.ListenableFuture} return type
* or a corresponding JDK 8 {@link java.util.concurrent.CompletableFuture} /
* {@link java.util.concurrent.CompletionStage} handle.
* *
* <h3>STOMP over WebSocket</h3> * <h3>STOMP over WebSocket</h3>
* <p>An {@link SendTo @SendTo} annotation is not strictly required &mdash; by default * <p>An {@link SendTo @SendTo} annotation is not strictly required &mdash; by default

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2016 the original author or authors. * Copyright 2002-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,28 +17,31 @@
package org.springframework.messaging.handler.invocation; package org.springframework.messaging.handler.invocation;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.util.concurrent.CompletableToListenableFutureAdapter; import org.springframework.util.concurrent.CompletableToListenableFutureAdapter;
import org.springframework.util.concurrent.ListenableFuture; import org.springframework.util.concurrent.ListenableFuture;
/** /**
* Support for {@link CompletableFuture} as a return value type. * Support for {@link CompletableFuture} (and as of 4.3.7 also {@link CompletionStage})
* as a return value type.
* *
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Juergen Hoeller
* @since 4.2 * @since 4.2
*/ */
public class CompletableFutureReturnValueHandler extends AbstractAsyncReturnValueHandler { public class CompletableFutureReturnValueHandler extends AbstractAsyncReturnValueHandler {
@Override @Override
public boolean supportsReturnType(MethodParameter returnType) { public boolean supportsReturnType(MethodParameter returnType) {
return CompletableFuture.class.isAssignableFrom(returnType.getParameterType()); return CompletionStage.class.isAssignableFrom(returnType.getParameterType());
} }
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public ListenableFuture<?> toListenableFuture(Object returnValue, MethodParameter returnType) { public ListenableFuture<?> toListenableFuture(Object returnValue, MethodParameter returnType) {
return new CompletableToListenableFutureAdapter<>((CompletableFuture<Object>) returnValue); return new CompletableToListenableFutureAdapter<>((CompletionStage<Object>) returnValue);
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2016 the original author or authors. * Copyright 2002-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -270,7 +270,7 @@ public class SimpAnnotationMethodMessageHandlerTests {
this.messageHandler.handleMessage(message); this.messageHandler.handleMessage(message);
controller.future.run(); controller.future.run();
assertTrue(controller.exceptionCatched); assertTrue(controller.exceptionCaught);
} }
@Test @Test
@ -340,7 +340,6 @@ public class SimpAnnotationMethodMessageHandlerTests {
} }
private static class TestSimpAnnotationMethodMessageHandler extends SimpAnnotationMethodMessageHandler { private static class TestSimpAnnotationMethodMessageHandler extends SimpAnnotationMethodMessageHandler {
public TestSimpAnnotationMethodMessageHandler(SimpMessageSendingOperations brokerTemplate, public TestSimpAnnotationMethodMessageHandler(SimpMessageSendingOperations brokerTemplate,
@ -434,6 +433,7 @@ public class SimpAnnotationMethodMessageHandlerTests {
} }
} }
@Controller @Controller
@MessageMapping("pre") @MessageMapping("pre")
private static class DotPathSeparatorController { private static class DotPathSeparatorController {
@ -447,12 +447,14 @@ public class SimpAnnotationMethodMessageHandlerTests {
} }
} }
@Controller @Controller
@MessageMapping("listenable-future") @MessageMapping("listenable-future")
private static class ListenableFutureController { private static class ListenableFutureController {
private ListenableFutureTask<String> future; private ListenableFutureTask<String> future;
private boolean exceptionCatched = false;
private boolean exceptionCaught = false;
@MessageMapping("success") @MessageMapping("success")
public ListenableFutureTask<String> handleListenableFuture() { public ListenableFutureTask<String> handleListenableFuture() {
@ -470,10 +472,10 @@ public class SimpAnnotationMethodMessageHandlerTests {
@MessageExceptionHandler(IllegalStateException.class) @MessageExceptionHandler(IllegalStateException.class)
public void handleValidationException() { public void handleValidationException() {
this.exceptionCatched = true; this.exceptionCaught = true;
}
} }
}
@Controller @Controller
private static class CompletableFutureController { private static class CompletableFutureController {
@ -492,14 +494,14 @@ public class SimpAnnotationMethodMessageHandlerTests {
public void handleValidationException() { public void handleValidationException() {
this.exceptionCaught = true; this.exceptionCaught = true;
} }
} }
private static class StringTestValidator implements Validator { private static class StringTestValidator implements Validator {
private final String invalidValue; private final String invalidValue;
private StringTestValidator(String invalidValue) { public StringTestValidator(String invalidValue) {
this.invalidValue = invalidValue; this.invalidValue = invalidValue;
} }

View File

@ -1439,8 +1439,8 @@ The following are the supported return types:
asynchronously in a thread managed by Spring MVC. asynchronously in a thread managed by Spring MVC.
* A `DeferredResult<?>` can be returned when the application wants to produce the return * A `DeferredResult<?>` can be returned when the application wants to produce the return
value from a thread of its own choosing. value from a thread of its own choosing.
* A `ListenableFuture<?>` can be returned when the application wants to produce the return * A `ListenableFuture<?>` or `CompletableFuture<?>`/`CompletionStage<?>` can be returned
value from a thread of its own choosing. when the application wants to produce the value from a thread pool submission.
* A `ResponseBodyEmitter` can be returned to write multiple objects to the response * A `ResponseBodyEmitter` can be returned to write multiple objects to the response
asynchronously; also supported as the body within a `ResponseEntity`. asynchronously; also supported as the body within a `ResponseEntity`.
* An `SseEmitter` can be returned to write Server-Sent Events to the response * An `SseEmitter` can be returned to write Server-Sent Events to the response

View File

@ -1354,7 +1354,7 @@ the declared method argument type as necessary.
* `java.security.Principal` method arguments reflecting the user logged in at * `java.security.Principal` method arguments reflecting the user logged in at
the time of the WebSocket HTTP handshake. the time of the WebSocket HTTP handshake.
The return value from an `@MessageMapping` method is converted with a A return value from an `@MessageMapping` method will be converted with a
`org.springframework.messaging.converter.MessageConverter` and used as the body `org.springframework.messaging.converter.MessageConverter` and used as the body
of a new message that is then sent, by default, to the `"brokerChannel"` with of a new message that is then sent, by default, to the `"brokerChannel"` with
the same destination as the client message but using the prefix `"/topic"` by the same destination as the client message but using the prefix `"/topic"` by
@ -1362,8 +1362,12 @@ default. An `@SendTo` message level annotation can be used to specify any
other destination instead. It can also be set a class-level to share a common other destination instead. It can also be set a class-level to share a common
destination. destination.
An `@SubscribeMapping` annotation can also be used to map subscription requests A response message may also be provided asynchronously via a `ListenableFuture`
to `@Controller` methods. It is supported on the method level, but can also be or `CompletableFuture`/`CompletionStage` return type signature, analogous to
deferred results in an MVC handler method.
A `@SubscribeMapping` annotation can be used to map subscription requests to
`@Controller` methods. It is supported on the method level, but can also be
combined with a type level `@MessageMapping` annotation that expresses shared combined with a type level `@MessageMapping` annotation that expresses shared
mappings across all message handling methods within the same controller. mappings across all message handling methods within the same controller.