Skip to content

Commit 0623172

Browse files
committed
Consistently support CompletionStage next to CompletableFuture
Issue: SPR-15258 (cherry picked from commit 50d93d3)
1 parent 4d2360e commit 0623172

File tree

6 files changed

+49
-22
lines changed

6 files changed

+49
-22
lines changed

spring-core/src/main/java/org/springframework/util/concurrent/CompletableToListenableFutureAdapter.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.util.concurrent;
1818

1919
import java.util.concurrent.CompletableFuture;
20+
import java.util.concurrent.CompletionStage;
2021
import java.util.concurrent.ExecutionException;
2122
import java.util.concurrent.TimeUnit;
2223
import java.util.concurrent.TimeoutException;
@@ -25,9 +26,11 @@
2526
import org.springframework.lang.UsesJava8;
2627

2728
/**
28-
* Adapts a {@link CompletableFuture} into a {@link ListenableFuture}.
29+
* Adapts a {@link CompletableFuture} or {@link CompletionStage} into a
30+
* Spring {@link ListenableFuture}.
2931
*
3032
* @author Sebastien Deleuze
33+
* @author Juergen Hoeller
3134
* @since 4.2
3235
*/
3336
@UsesJava8
@@ -38,6 +41,17 @@ public class CompletableToListenableFutureAdapter<T> implements ListenableFuture
3841
private final ListenableFutureCallbackRegistry<T> callbacks = new ListenableFutureCallbackRegistry<T>();
3942

4043

44+
/**
45+
* Create a new adapter for the given {@link CompletionStage}.
46+
* @since 4.3.7
47+
*/
48+
public CompletableToListenableFutureAdapter(CompletionStage<T> completionStage) {
49+
this(completionStage.toCompletableFuture());
50+
}
51+
52+
/**
53+
* Create a new adapter for the given {@link CompletableFuture}.
54+
*/
4155
public CompletableToListenableFutureAdapter(CompletableFuture<T> completableFuture) {
4256
this.completableFuture = completableFuture;
4357
this.completableFuture.handle(new BiFunction<T, Throwable, Object>() {

spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMapping.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -66,8 +66,12 @@
6666
* HTTP handshake that initiates WebSocket sessions.</li>
6767
* </ul>
6868
*
69-
* <p>By default the return value is wrapped as a message and sent to the destination
70-
* specified with an {@link SendTo @SendTo} method-level annotation.
69+
* <p>A return value will get wrapped as a message and sent to a default response
70+
* destination or to a custom destination specified with an {@link SendTo @SendTo}
71+
* method-level annotation. Such a response may also be provided asynchronously
72+
* via a {@link org.springframework.util.concurrent.ListenableFuture} return type
73+
* or a corresponding JDK 8 {@link java.util.concurrent.CompletableFuture} /
74+
* {@link java.util.concurrent.CompletionStage} handle.
7175
*
7276
* <h3>STOMP over WebSocket</h3>
7377
* <p>An {@link SendTo @SendTo} annotation is not strictly required &mdash; by default

spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/CompletableFutureReturnValueHandler.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,30 +17,33 @@
1717
package org.springframework.messaging.handler.invocation;
1818

1919
import java.util.concurrent.CompletableFuture;
20+
import java.util.concurrent.CompletionStage;
2021

2122
import org.springframework.core.MethodParameter;
2223
import org.springframework.lang.UsesJava8;
2324
import org.springframework.util.concurrent.CompletableToListenableFutureAdapter;
2425
import org.springframework.util.concurrent.ListenableFuture;
2526

2627
/**
27-
* Support for {@link CompletableFuture} as a return value type.
28+
* Support for {@link CompletableFuture} (and as of 4.3.7 also {@link CompletionStage})
29+
* as a return value type.
2830
*
2931
* @author Sebastien Deleuze
32+
* @author Juergen Hoeller
3033
* @since 4.2
3134
*/
3235
@UsesJava8
3336
public class CompletableFutureReturnValueHandler extends AbstractAsyncReturnValueHandler {
3437

3538
@Override
3639
public boolean supportsReturnType(MethodParameter returnType) {
37-
return CompletableFuture.class.isAssignableFrom(returnType.getParameterType());
40+
return CompletionStage.class.isAssignableFrom(returnType.getParameterType());
3841
}
3942

4043
@Override
4144
@SuppressWarnings("unchecked")
4245
public ListenableFuture<?> toListenableFuture(Object returnValue, MethodParameter returnType) {
43-
return new CompletableToListenableFutureAdapter<Object>((CompletableFuture<Object>) returnValue);
46+
return new CompletableToListenableFutureAdapter<Object>((CompletionStage<Object>) returnValue);
4447
}
4548

4649
}

spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandlerTests.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -270,7 +270,7 @@ public void listenableFutureFailure() {
270270
this.messageHandler.handleMessage(message);
271271

272272
controller.future.run();
273-
assertTrue(controller.exceptionCatched);
273+
assertTrue(controller.exceptionCaught);
274274
}
275275

276276
@Test
@@ -340,7 +340,6 @@ private Message<?> createMessage(String destination, Map<String, Object> headers
340340
}
341341

342342

343-
344343
private static class TestSimpAnnotationMethodMessageHandler extends SimpAnnotationMethodMessageHandler {
345344

346345
public TestSimpAnnotationMethodMessageHandler(SimpMessageSendingOperations brokerTemplate,
@@ -434,6 +433,7 @@ public void placeholder() {
434433
}
435434
}
436435

436+
437437
@Controller
438438
@MessageMapping("pre")
439439
private static class DotPathSeparatorController {
@@ -447,12 +447,14 @@ public void handleFoo() {
447447
}
448448
}
449449

450+
450451
@Controller
451452
@MessageMapping("listenable-future")
452453
private static class ListenableFutureController {
453454

454455
private ListenableFutureTask<String> future;
455-
private boolean exceptionCatched = false;
456+
457+
private boolean exceptionCaught = false;
456458

457459
@MessageMapping("success")
458460
public ListenableFutureTask<String> handleListenableFuture() {
@@ -470,11 +472,11 @@ public ListenableFutureTask<String> handleListenableFutureException() {
470472

471473
@MessageExceptionHandler(IllegalStateException.class)
472474
public void handleValidationException() {
473-
this.exceptionCatched = true;
475+
this.exceptionCaught = true;
474476
}
475-
476477
}
477478

479+
478480
@Controller
479481
private static class CompletableFutureController {
480482

@@ -492,14 +494,14 @@ public CompletableFuture<String> handleCompletableFuture() {
492494
public void handleValidationException() {
493495
this.exceptionCaught = true;
494496
}
495-
496497
}
497498

499+
498500
private static class StringTestValidator implements Validator {
499501

500502
private final String invalidValue;
501503

502-
private StringTestValidator(String invalidValue) {
504+
public StringTestValidator(String invalidValue) {
503505
this.invalidValue = invalidValue;
504506
}
505507

src/asciidoc/web-mvc.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,8 +1436,8 @@ The following are the supported return types:
14361436
asynchronously in a thread managed by Spring MVC.
14371437
* A `DeferredResult<?>` can be returned when the application wants to produce the return
14381438
value from a thread of its own choosing.
1439-
* A `ListenableFuture<?>` can be returned when the application wants to produce the return
1440-
value from a thread of its own choosing.
1439+
* A `ListenableFuture<?>` or `CompletableFuture<?>`/`CompletionStage<?>` can be returned
1440+
when the application wants to produce the value from a thread pool submission.
14411441
* A `ResponseBodyEmitter` can be returned to write multiple objects to the response
14421442
asynchronously; also supported as the body within a `ResponseEntity`.
14431443
* An `SseEmitter` can be returned to write Server-Sent Events to the response

src/asciidoc/web-websocket.adoc

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,16 +1354,20 @@ the declared method argument type as necessary.
13541354
* `java.security.Principal` method arguments reflecting the user logged in at
13551355
the time of the WebSocket HTTP handshake.
13561356

1357-
The return value from an `@MessageMapping` method is converted with a
1357+
A return value from an `@MessageMapping` method will be converted with a
13581358
`org.springframework.messaging.converter.MessageConverter` and used as the body
13591359
of a new message that is then sent, by default, to the `"brokerChannel"` with
13601360
the same destination as the client message but using the prefix `"/topic"` by
13611361
default. An `@SendTo` message level annotation can be used to specify any
13621362
other destination instead. It can also be set a class-level to share a common
13631363
destination.
13641364

1365-
An `@SubscribeMapping` annotation can also be used to map subscription requests
1366-
to `@Controller` methods. It is supported on the method level, but can also be
1365+
A response message may also be provided asynchronously via a `ListenableFuture`
1366+
or `CompletableFuture`/`CompletionStage` return type signature, analogous to
1367+
deferred results in an MVC handler method.
1368+
1369+
A `@SubscribeMapping` annotation can be used to map subscription requests to
1370+
`@Controller` methods. It is supported on the method level, but can also be
13671371
combined with a type level `@MessageMapping` annotation that expresses shared
13681372
mappings across all message handling methods within the same controller.
13691373

0 commit comments

Comments
 (0)