Deprecate ListenableFuture in favor of CompletableFuture

This commit deprecates ListenableFuture in favor of CompletableFuture.
ListenableFuture was introduced in Spring Framework 4.0, when
CompletableFuture was not yet available. Spring now requires JDK 17, so
having our own type no longer seems necessary.

Major changes in this commit include:
- Deprecation of ListenableFuture and related types
  (ListenableFutureCallback, SettableListenableFuture, etc.)
- Deprecation of AsyncListenableTaskExecutor in favor of default methods
  in AsyncTaskExecutor (submitCompletable).
- AsyncHandlerMethodReturnValueHandler now has toCompletableFuture
  instead of toListenableFuture.
- WebSocketClient now has execute methods, which do the same as
  doHandshake, but return CompletableFutures (cf. the reactive
  WebSocketClient).

All other changes
- add an overloaded method that takes a CompletableFuture parameter
  instead of ListenableFuture, and/or
- add a method with a 'Async' suffix that returns a CompletableFuture
  instead of a ListenableFuture (connectAsync, sendAsync).

Closes gh-27780
This commit is contained in:
Arjen Poutsma
2022-03-17 12:18:00 +01:00
parent 735051bf7d
commit 2aa74c9121
74 changed files with 1148 additions and 380 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -26,7 +26,9 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -54,8 +56,6 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
/**
* Abstract base class for HandlerMethod-based message handling. Provides most of
@@ -571,9 +571,9 @@ public abstract class AbstractMethodMessageHandler<T>
return;
}
if (returnValue != null && this.returnValueHandlers.isAsyncReturnValue(returnValue, returnType)) {
ListenableFuture<?> future = this.returnValueHandlers.toListenableFuture(returnValue, returnType);
CompletableFuture<?> future = this.returnValueHandlers.toCompletableFuture(returnValue, returnType);
if (future != null) {
future.addCallback(new ReturnValueListenableFutureCallback(invocable, message));
future.whenComplete(new ReturnValueListenableFutureCallback(invocable, message));
}
}
else {
@@ -704,7 +704,7 @@ public abstract class AbstractMethodMessageHandler<T>
}
private class ReturnValueListenableFutureCallback implements ListenableFutureCallback<Object> {
private class ReturnValueListenableFutureCallback implements BiConsumer<Object, Throwable> {
private final InvocableHandlerMethod handlerMethod;
@@ -716,21 +716,21 @@ public abstract class AbstractMethodMessageHandler<T>
}
@Override
public void onSuccess(@Nullable Object result) {
try {
MethodParameter returnType = this.handlerMethod.getAsyncReturnValueType(result);
returnValueHandlers.handleReturnValue(result, returnType, this.message);
public void accept(@Nullable Object result, @Nullable Throwable ex) {
if (result != null) {
try {
MethodParameter returnType = this.handlerMethod.getAsyncReturnValueType(result);
returnValueHandlers.handleReturnValue(result, returnType, this.message);
}
catch (Throwable throwable) {
handleFailure(throwable);
}
}
catch (Throwable ex) {
else if (ex != null) {
handleFailure(ex);
}
}
@Override
public void onFailure(Throwable ex) {
handleFailure(ex);
}
private void handleFailure(Throwable ex) {
Exception cause = (ex instanceof Exception ? (Exception) ex : new IllegalStateException(ex));
processHandlerMethodException(this.handlerMethod, cause, this.message);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,8 +16,11 @@
package org.springframework.messaging.handler.invocation;
import java.util.concurrent.CompletableFuture;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.util.concurrent.CompletableToListenableFutureAdapter;
import org.springframework.util.concurrent.ListenableFuture;
/**
@@ -37,7 +40,7 @@ public interface AsyncHandlerMethodReturnValueHandler extends HandlerMethodRetur
/**
* Whether the return value represents an asynchronous, Future-like type
* with success and error callbacks. If this method returns {@code true},
* then {@link #toListenableFuture} is invoked next. If it returns
* then {@link #toCompletableFuture} is invoked next. If it returns
* {@code false}, then {@link #handleReturnValue} is called.
* <p><strong>Note:</strong> this method will only be invoked after
* {@link #supportsReturnType(org.springframework.core.MethodParameter)}
@@ -61,8 +64,30 @@ public interface AsyncHandlerMethodReturnValueHandler extends HandlerMethodRetur
* @param returnType the type of the return value
* @return the resulting ListenableFuture, or {@code null} in which case
* no further handling will be performed
* @deprecated as of 6.0, in favor of
* {@link #toCompletableFuture(Object, MethodParameter)}
*/
@Deprecated
@Nullable
default ListenableFuture<?> toListenableFuture(Object returnValue, MethodParameter returnType) {
CompletableFuture<?> result = toCompletableFuture(returnValue, returnType);
return (result != null) ? new CompletableToListenableFutureAdapter<>(result) : null;
}
/**
* Adapt the asynchronous return value to a {@link CompletableFuture}.
* Return value handling will then continue when
* the CompletableFuture is completed with either success or error.
* <p><strong>Note:</strong> this method will only be invoked after
* {@link #supportsReturnType(org.springframework.core.MethodParameter)}
* is called and it returns {@code true}.
* @param returnValue the value returned from the handler method
* @param returnType the type of the return value
* @return the resulting CompletableFuture, or {@code null} in which case
* no further handling will be performed
* @since 6.0
*/
@Nullable
ListenableFuture<?> toListenableFuture(Object returnValue, MethodParameter returnType);
CompletableFuture<?> toCompletableFuture(Object returnValue, MethodParameter returnType);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2022 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.
@@ -20,8 +20,6 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import org.springframework.core.MethodParameter;
import org.springframework.util.concurrent.CompletableToListenableFutureAdapter;
import org.springframework.util.concurrent.ListenableFuture;
/**
* Support for {@link CompletableFuture} (and as of 4.3.7 also {@link CompletionStage})
@@ -39,9 +37,7 @@ public class CompletableFutureReturnValueHandler extends AbstractAsyncReturnValu
}
@Override
@SuppressWarnings("unchecked")
public ListenableFuture<?> toListenableFuture(Object returnValue, MethodParameter returnType) {
return new CompletableToListenableFutureAdapter<>((CompletionStage<Object>) returnValue);
public CompletableFuture<?> toCompletableFuture(Object returnValue, MethodParameter returnType) {
return ((CompletionStage<?>) returnValue).toCompletableFuture();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -19,6 +19,7 @@ package org.springframework.messaging.handler.invocation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -26,7 +27,6 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.util.concurrent.ListenableFuture;
/**
* A HandlerMethodReturnValueHandler that wraps and delegates to others.
@@ -135,12 +135,11 @@ public class HandlerMethodReturnValueHandlerComposite implements AsyncHandlerMet
@Override
@Nullable
public ListenableFuture<?> toListenableFuture(Object returnValue, MethodParameter returnType) {
public CompletableFuture<?> toCompletableFuture(Object returnValue, MethodParameter returnType) {
HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType);
if (handler instanceof AsyncHandlerMethodReturnValueHandler) {
return ((AsyncHandlerMethodReturnValueHandler) handler).toListenableFuture(returnValue, returnType);
return ((AsyncHandlerMethodReturnValueHandler) handler).toCompletableFuture(returnValue, returnType);
}
return null;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,6 +16,8 @@
package org.springframework.messaging.handler.invocation;
import java.util.concurrent.CompletableFuture;
import org.springframework.core.MethodParameter;
import org.springframework.util.concurrent.ListenableFuture;
@@ -24,7 +26,9 @@ import org.springframework.util.concurrent.ListenableFuture;
*
* @author Sebastien Deleuze
* @since 4.2
* @deprecated as of 6.0, in favor of {@link CompletableFutureReturnValueHandler}
*/
@Deprecated
public class ListenableFutureReturnValueHandler extends AbstractAsyncReturnValueHandler {
@Override
@@ -38,4 +42,8 @@ public class ListenableFutureReturnValueHandler extends AbstractAsyncReturnValue
return (ListenableFuture<?>) returnValue;
}
@Override
public CompletableFuture<?> toCompletableFuture(Object returnValue, MethodParameter returnType) {
return ((ListenableFuture<?>) returnValue).completable();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,13 +16,13 @@
package org.springframework.messaging.handler.invocation;
import java.util.concurrent.CompletableFuture;
import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.MonoToListenableFutureAdapter;
/**
* Support for single-value reactive types (like {@code Mono} or {@code Single})
@@ -57,12 +57,11 @@ public class ReactiveReturnValueHandler extends AbstractAsyncReturnValueHandler
}
@Override
public ListenableFuture<?> toListenableFuture(Object returnValue, MethodParameter returnType) {
public CompletableFuture<?> toCompletableFuture(Object returnValue, MethodParameter returnType) {
ReactiveAdapter adapter = this.adapterRegistry.getAdapter(returnType.getParameterType(), returnValue);
if (adapter != null) {
return new MonoToListenableFutureAdapter<>(Mono.from(adapter.toPublisher(returnValue)));
return Mono.from(adapter.toPublisher(returnValue)).toFuture();
}
return null;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 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.
@@ -326,6 +326,7 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
return resolvers;
}
@SuppressWarnings("deprecation")
@Override
protected List<? extends HandlerMethodReturnValueHandler> initReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,6 +16,9 @@
package org.springframework.messaging.simp.stomp;
import java.util.concurrent.CompletableFuture;
import org.springframework.util.concurrent.CompletableToListenableFutureAdapter;
import org.springframework.util.concurrent.ListenableFuture;
/**
@@ -33,7 +36,17 @@ public interface ConnectionHandlingStompSession extends StompSession, StompTcpCo
/**
* Return a future that will complete when the session is ready for use.
* @deprecated as of 6.0, in favor of {@link #getSession()}
*/
ListenableFuture<StompSession> getSessionFuture();
@Deprecated
default ListenableFuture<StompSession> getSessionFuture() {
return new CompletableToListenableFutureAdapter<>(getSession());
}
/**
* Return a future that will complete when the session is ready for use.
* @since 6.0
*/
CompletableFuture<StompSession> getSession();
}

View File

@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
@@ -47,9 +48,6 @@ import org.springframework.util.Assert;
import org.springframework.util.IdGenerator;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.util.concurrent.SettableListenableFuture;
/**
* Default implementation of {@link ConnectionHandlingStompSession}.
@@ -85,7 +83,7 @@ public class DefaultStompSession implements ConnectionHandlingStompSession {
private final StompHeaders connectHeaders;
private final SettableListenableFuture<StompSession> sessionFuture = new SettableListenableFuture<>();
private final CompletableFuture<StompSession> sessionFuture = new CompletableFuture<>();
private MessageConverter converter = new SimpleMessageConverter();
@@ -149,7 +147,7 @@ public class DefaultStompSession implements ConnectionHandlingStompSession {
}
@Override
public ListenableFuture<StompSession> getSessionFuture() {
public CompletableFuture<StompSession> getSession() {
return this.sessionFuture;
}
@@ -289,7 +287,7 @@ public class DefaultStompSession implements ConnectionHandlingStompSession {
TcpConnection<byte[]> conn = this.connection;
Assert.state(conn != null, "Connection closed");
try {
conn.send(message).get();
conn.sendAsync(message).get();
}
catch (ExecutionException ex) {
throw new MessageDeliveryException(message, ex.getCause());
@@ -407,7 +405,7 @@ public class DefaultStompSession implements ConnectionHandlingStompSession {
if (logger.isDebugEnabled()) {
logger.debug("Failed to connect session id=" + this.sessionId, ex);
}
this.sessionFuture.setException(ex);
this.sessionFuture.completeExceptionally(ex);
this.sessionHandler.handleTransportError(this, ex);
}
@@ -450,7 +448,7 @@ public class DefaultStompSession implements ConnectionHandlingStompSession {
else if (StompCommand.CONNECTED.equals(command)) {
initHeartbeatTasks(headers);
this.version = headers.getFirst("version");
this.sessionFuture.set(this);
this.sessionFuture.complete(this);
this.sessionHandler.afterConnected(this, headers);
}
else if (StompCommand.ERROR.equals(command)) {
@@ -506,7 +504,7 @@ public class DefaultStompSession implements ConnectionHandlingStompSession {
@Override
public void handleFailure(Throwable ex) {
try {
this.sessionFuture.setException(ex); // no-op if already set
this.sessionFuture.completeExceptionally(ex); // no-op if already set
this.sessionHandler.handleTransportError(this, ex);
}
catch (Throwable ex2) {
@@ -698,16 +696,11 @@ public class DefaultStompSession implements ConnectionHandlingStompSession {
public void run() {
TcpConnection<byte[]> conn = connection;
if (conn != null) {
conn.send(HEARTBEAT).addCallback(
new ListenableFutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void result) {
}
@Override
public void onFailure(Throwable ex) {
handleFailure(ex);
}
});
conn.sendAsync(HEARTBEAT).whenComplete((unused, throwable) -> {
if (throwable != null) {
handleFailure(throwable);
}
});
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,11 +16,14 @@
package org.springframework.messaging.simp.stomp;
import java.util.concurrent.CompletableFuture;
import org.springframework.lang.Nullable;
import org.springframework.messaging.simp.SimpLogging;
import org.springframework.messaging.tcp.TcpOperations;
import org.springframework.messaging.tcp.reactor.ReactorNettyTcpClient;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.CompletableToListenableFutureAdapter;
import org.springframework.util.concurrent.ListenableFuture;
/**
@@ -71,9 +74,22 @@ public class ReactorNettyTcpStompClient extends StompClientSupport {
* on the STOMP level.
* @param handler the handler for the STOMP session
* @return a ListenableFuture for access to the session when ready for use
* @deprecated as of 6.0, in favor of {@link #connectAsync(StompSessionHandler)}
*/
@Deprecated
public ListenableFuture<StompSession> connect(StompSessionHandler handler) {
return connect(null, handler);
return new CompletableToListenableFutureAdapter<>(connectAsync(handler));
}
/**
* Connect and notify the given {@link StompSessionHandler} when connected
* on the STOMP level.
* @param handler the handler for the STOMP session
* @return a ListenableFuture for access to the session when ready for use
* @since 6.0
*/
public CompletableFuture<StompSession> connectAsync(StompSessionHandler handler) {
return connectAsync(null, handler);
}
/**
@@ -82,18 +98,33 @@ public class ReactorNettyTcpStompClient extends StompClientSupport {
* @param connectHeaders headers to add to the CONNECT frame
* @param handler the handler for the STOMP session
* @return a ListenableFuture for access to the session when ready for use
* @deprecated as of 6.0, in favor of {@link #connectAsync(StompHeaders, StompSessionHandler)}
*/
@Deprecated
public ListenableFuture<StompSession> connect(@Nullable StompHeaders connectHeaders, StompSessionHandler handler) {
ConnectionHandlingStompSession session = createSession(connectHeaders, handler);
this.tcpClient.connect(session);
this.tcpClient.connectAsync(session);
return session.getSessionFuture();
}
/**
* An overloaded version of {@link #connectAsync(StompSessionHandler)} that
* accepts headers to use for the STOMP CONNECT frame.
* @param connectHeaders headers to add to the CONNECT frame
* @param handler the handler for the STOMP session
* @return a CompletableFuture for access to the session when ready for use
*/
public CompletableFuture<StompSession> connectAsync(@Nullable StompHeaders connectHeaders, StompSessionHandler handler) {
ConnectionHandlingStompSession session = createSession(connectHeaders, handler);
this.tcpClient.connectAsync(session);
return session.getSession();
}
/**
* Shut down the client and release resources.
*/
public void shutdown() {
this.tcpClient.shutdown();
this.tcpClient.shutdownAsync();
}
@Override

View File

@@ -22,6 +22,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@@ -47,9 +48,6 @@ import org.springframework.messaging.tcp.reactor.ReactorNettyCodec;
import org.springframework.messaging.tcp.reactor.ReactorNettyTcpClient;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.util.concurrent.ListenableFutureTask;
/**
* A {@link org.springframework.messaging.MessageHandler} that handles messages by
@@ -98,14 +96,13 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
private static final byte[] EMPTY_PAYLOAD = new byte[0];
private static final ListenableFutureTask<Void> EMPTY_TASK = new ListenableFutureTask<>(new VoidCallable());
private static final CompletableFuture<Void> EMPTY_TASK = CompletableFuture.completedFuture(null);
private static final StompHeaderAccessor HEART_BEAT_ACCESSOR;
private static final Message<byte[]> HEARTBEAT_MESSAGE;
static {
EMPTY_TASK.run();
HEART_BEAT_ACCESSOR = StompHeaderAccessor.createForHeartbeat();
HEARTBEAT_MESSAGE = MessageBuilder.createMessage(
StompDecoder.HEARTBEAT_PAYLOAD, HEART_BEAT_ACCESSOR.getMessageHeaders());
@@ -455,7 +452,7 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
this.connectionHandlers.put(handler.getSessionId(), handler);
this.stats.incrementConnectCount();
this.tcpClient.connect(handler, new FixedIntervalReconnectStrategy(5000));
this.tcpClient.connectAsync(handler, new FixedIntervalReconnectStrategy(5000));
if (this.taskScheduler != null) {
this.taskScheduler.scheduleWithFixedDelay(new ClientSendMessageCountTask(), Duration.ofMillis(5000));
@@ -478,7 +475,7 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
publishBrokerUnavailableEvent();
if (this.tcpClient != null) {
try {
this.tcpClient.shutdown().get(5000, TimeUnit.MILLISECONDS);
this.tcpClient.shutdownAsync().get(5000, TimeUnit.MILLISECONDS);
}
catch (Throwable ex) {
logger.error("Error in shutdown of TCP client", ex);
@@ -572,7 +569,7 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
this.connectionHandlers.put(sessionId, handler);
this.stats.incrementConnectCount();
Assert.state(this.tcpClient != null, "No TCP client available");
this.tcpClient.connect(handler);
this.tcpClient.connectAsync(handler);
}
else if (StompCommand.DISCONNECT.equals(command)) {
RelayConnectionHandler handler = this.connectionHandlers.get(sessionId);
@@ -691,7 +688,7 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
MAX_TIME_TO_CONNECTED_FRAME + " ms.", null);
}
}, MAX_TIME_TO_CONNECTED_FRAME);
connection.send(MessageBuilder.createMessage(EMPTY_PAYLOAD, this.connectHeaders.getMessageHeaders()));
connection.sendAsync(MessageBuilder.createMessage(EMPTY_PAYLOAD, this.connectHeaders.getMessageHeaders()));
}
@Override
@@ -865,7 +862,7 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
* @return a future to wait for the result
*/
@SuppressWarnings("unchecked")
public ListenableFuture<Void> forward(final Message<?> message, final StompHeaderAccessor accessor) {
public CompletableFuture<Void> forward(final Message<?> message, final StompHeaderAccessor accessor) {
TcpConnection<byte[]> conn = this.tcpConnection;
if (!this.isStompConnected || conn == null) {
@@ -901,19 +898,17 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
logger.trace("Forwarding " + accessor.getDetailedLogMessage(message.getPayload()));
}
ListenableFuture<Void> future = conn.send((Message<byte[]>) messageToSend);
future.addCallback(new ListenableFutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void result) {
CompletableFuture<Void> future = conn.sendAsync((Message<byte[]>) messageToSend);
future.whenComplete((unused, throwable) -> {
if (throwable == null) {
if (accessor.getCommand() == StompCommand.DISCONNECT) {
afterDisconnectSent(accessor);
}
}
@Override
public void onFailure(Throwable ex) {
if (tcpConnection != null) {
else {
if (this.tcpConnection != null) {
handleTcpConnectionFailure("failed to forward " +
accessor.getShortLogMessage(message.getPayload()), ex);
accessor.getShortLogMessage(message.getPayload()), throwable);
}
else if (logger.isErrorEnabled()) {
logger.error("Failed to forward " + accessor.getShortLogMessage(message.getPayload()));
@@ -1005,10 +1000,11 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
if (clientSendInterval > 0 && serverReceiveInterval > 0) {
long interval = Math.max(clientSendInterval, serverReceiveInterval);
con.onWriteInactivity(() ->
con.send(HEARTBEAT_MESSAGE).addCallback(
result -> {},
ex -> handleTcpConnectionFailure(
"Failed to forward heartbeat: " + ex.getMessage(), ex)), interval);
con.sendAsync(HEARTBEAT_MESSAGE).whenComplete((unused, ex) -> {
if (ex != null) {
handleTcpConnectionFailure("Failed to forward heartbeat: " + ex.getMessage(), ex);
}
}), interval);
}
if (clientReceiveInterval > 0 && serverSendInterval > 0) {
final long interval = Math.max(clientReceiveInterval, serverSendInterval) * HEARTBEAT_MULTIPLIER;
@@ -1029,12 +1025,11 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
TcpConnection<byte[]> conn = getTcpConnection();
if (conn != null) {
MessageHeaders headers = accessor.getMessageHeaders();
conn.send(MessageBuilder.createMessage(EMPTY_PAYLOAD, headers)).addCallback(
result -> {},
ex -> {
String error = "Failed to subscribe in \"system\" session.";
handleTcpConnectionFailure(error, ex);
});
conn.sendAsync(MessageBuilder.createMessage(EMPTY_PAYLOAD, headers)).whenComplete((unused, ex) -> {
if (ex != null) {
handleTcpConnectionFailure("Failed to subscribe in \"system\" session.", ex);
}
});
}
}
}
@@ -1083,9 +1078,9 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
}
@Override
public ListenableFuture<Void> forward(Message<?> message, StompHeaderAccessor accessor) {
public CompletableFuture<Void> forward(Message<?> message, StompHeaderAccessor accessor) {
try {
ListenableFuture<Void> future = super.forward(message, accessor);
CompletableFuture<Void> future = super.forward(message, accessor);
if (message.getHeaders().get(SimpMessageHeaderAccessor.IGNORE_ERROR) == null) {
future.get();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 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.
@@ -17,8 +17,10 @@
package org.springframework.messaging.tcp;
import java.io.Closeable;
import java.util.concurrent.CompletableFuture;
import org.springframework.messaging.Message;
import org.springframework.util.concurrent.CompletableToListenableFutureAdapter;
import org.springframework.util.concurrent.ListenableFuture;
/**
@@ -35,8 +37,21 @@ public interface TcpConnection<P> extends Closeable {
* @param message the message
* @return a ListenableFuture that can be used to determine when and if the
* message was successfully sent
* @deprecated as of 6.0, in favor of {@link #sendAsync(Message)}
*/
ListenableFuture<Void> send(Message<P> message);
@Deprecated
default ListenableFuture<Void> send(Message<P> message) {
return new CompletableToListenableFutureAdapter<>(sendAsync(message));
}
/**
* Send the given message.
* @param message the message
* @return a CompletableFuture that can be used to determine when and if the
* message was successfully sent
* @since 6.0
*/
CompletableFuture<Void> sendAsync(Message<P> message);
/**
* Register a task to invoke after a period of read inactivity.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,6 +16,9 @@
package org.springframework.messaging.tcp;
import java.util.concurrent.CompletableFuture;
import org.springframework.util.concurrent.CompletableToListenableFutureAdapter;
import org.springframework.util.concurrent.ListenableFuture;
/**
@@ -32,8 +35,21 @@ public interface TcpOperations<P> {
* @param connectionHandler a handler to manage the connection
* @return a ListenableFuture that can be used to determine when and if the
* connection is successfully established
* @deprecated as of 6.0, in favor of {@link #connectAsync(TcpConnectionHandler)}
*/
ListenableFuture<Void> connect(TcpConnectionHandler<P> connectionHandler);
@Deprecated
default ListenableFuture<Void> connect(TcpConnectionHandler<P> connectionHandler) {
return new CompletableToListenableFutureAdapter<>(connectAsync(connectionHandler));
}
/**
* Open a new connection.
* @param connectionHandler a handler to manage the connection
* @return a CompletableFuture that can be used to determine when and if the
* connection is successfully established
* @since 6.0
*/
CompletableFuture<Void> connectAsync(TcpConnectionHandler<P> connectionHandler);
/**
* Open a new connection and a strategy for reconnecting if the connection fails.
@@ -41,14 +57,40 @@ public interface TcpOperations<P> {
* @param reconnectStrategy a strategy for reconnecting
* @return a ListenableFuture that can be used to determine when and if the
* initial connection is successfully established
* @deprecated as of 6.0, in favor of {@link #connectAsync(TcpConnectionHandler, ReconnectStrategy)}
*/
ListenableFuture<Void> connect(TcpConnectionHandler<P> connectionHandler, ReconnectStrategy reconnectStrategy);
@Deprecated
default ListenableFuture<Void> connect(TcpConnectionHandler<P> connectionHandler, ReconnectStrategy reconnectStrategy) {
return new CompletableToListenableFutureAdapter<>(connectAsync(connectionHandler, reconnectStrategy));
}
/**
* Open a new connection and a strategy for reconnecting if the connection fails.
* @param connectionHandler a handler to manage the connection
* @param reconnectStrategy a strategy for reconnecting
* @return a CompletableFuture that can be used to determine when and if the
* initial connection is successfully established
* @since 6.0
*/
CompletableFuture<Void> connectAsync(TcpConnectionHandler<P> connectionHandler, ReconnectStrategy reconnectStrategy);
/**
* Shut down and close any open connections.
* @return a ListenableFuture that can be used to determine when and if the
* connection is successfully closed
* @deprecated as of 6.0, in favor of {@link #shutdownAsync()}
*/
ListenableFuture<Void> shutdown();
@Deprecated
default ListenableFuture<Void> shutdown() {
return new CompletableToListenableFutureAdapter<>(shutdownAsync());
}
/**
* Shut down and close any open connections.
* @return a ListenableFuture that can be used to determine when and if the
* connection is successfully closed
* @since 6.0
*/
CompletableFuture<Void> shutdownAsync();
}

View File

@@ -52,10 +52,6 @@ import org.springframework.messaging.tcp.TcpConnection;
import org.springframework.messaging.tcp.TcpConnectionHandler;
import org.springframework.messaging.tcp.TcpOperations;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.CompletableToListenableFutureAdapter;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.MonoToListenableFutureAdapter;
import org.springframework.util.concurrent.SettableListenableFuture;
/**
* Reactor Netty based implementation of {@link TcpOperations}.
@@ -179,20 +175,18 @@ public class ReactorNettyTcpClient<P> implements TcpOperations<P> {
@Override
public ListenableFuture<Void> connect(final TcpConnectionHandler<P> handler) {
public CompletableFuture<Void> connectAsync(TcpConnectionHandler<P> handler) {
Assert.notNull(handler, "TcpConnectionHandler is required");
if (this.stopping) {
return handleShuttingDownConnectFailure(handler);
}
Mono<Void> connectMono = extendTcpClient(this.tcpClient, handler)
return extendTcpClient(this.tcpClient, handler)
.handle(new ReactorNettyHandler(handler))
.connect()
.doOnError(handler::afterConnectFailure)
.then();
return new MonoToListenableFutureAdapter<>(connectMono);
.then().toFuture();
}
/**
@@ -209,7 +203,7 @@ public class ReactorNettyTcpClient<P> implements TcpOperations<P> {
}
@Override
public ListenableFuture<Void> connect(TcpConnectionHandler<P> handler, ReconnectStrategy strategy) {
public CompletableFuture<Void> connectAsync(TcpConnectionHandler<P> handler, ReconnectStrategy strategy) {
Assert.notNull(handler, "TcpConnectionHandler is required");
Assert.notNull(strategy, "ReconnectStrategy is required");
@@ -234,14 +228,13 @@ public class ReactorNettyTcpClient<P> implements TcpOperations<P> {
.scan(1, (count, element) -> count++)
.flatMap(attempt -> reconnect(attempt, strategy)))
.subscribe();
return new CompletableToListenableFutureAdapter<>(connectFuture);
return connectFuture;
}
private ListenableFuture<Void> handleShuttingDownConnectFailure(TcpConnectionHandler<P> handler) {
private CompletableFuture<Void> handleShuttingDownConnectFailure(TcpConnectionHandler<P> handler) {
IllegalStateException ex = new IllegalStateException("Shutting down.");
handler.afterConnectFailure(ex);
return new MonoToListenableFutureAdapter<>(Mono.error(ex));
return Mono.<Void>error(ex).toFuture();
}
private Publisher<? extends Long> reconnect(Integer attempt, ReconnectStrategy reconnectStrategy) {
@@ -250,11 +243,9 @@ public class ReactorNettyTcpClient<P> implements TcpOperations<P> {
}
@Override
public ListenableFuture<Void> shutdown() {
public CompletableFuture<Void> shutdownAsync() {
if (this.stopping) {
SettableListenableFuture<Void> future = new SettableListenableFuture<>();
future.set(null);
return future;
return CompletableFuture.completedFuture(null);
}
this.stopping = true;
@@ -274,7 +265,7 @@ public class ReactorNettyTcpClient<P> implements TcpOperations<P> {
result = stopScheduler();
}
return new MonoToListenableFutureAdapter<>(result);
return result.toFuture();
}
private Mono<Void> stopScheduler() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,6 +16,8 @@
package org.springframework.messaging.tcp.reactor;
import java.util.concurrent.CompletableFuture;
import io.netty.buffer.ByteBuf;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;
@@ -24,8 +26,6 @@ import reactor.netty.NettyOutbound;
import org.springframework.messaging.Message;
import org.springframework.messaging.tcp.TcpConnection;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.MonoToListenableFutureAdapter;
/**
* Reactor Netty based implementation of {@link TcpConnection}.
@@ -56,11 +56,12 @@ public class ReactorNettyTcpConnection<P> implements TcpConnection<P> {
@Override
public ListenableFuture<Void> send(Message<P> message) {
public CompletableFuture<Void> sendAsync(Message<P> message) {
ByteBuf byteBuf = this.outbound.alloc().buffer();
this.codec.encode(message, byteBuf);
Mono<Void> sendCompletion = this.outbound.send(Mono.just(byteBuf)).then();
return new MonoToListenableFutureAdapter<>(sendCompletion);
return this.outbound.send(Mono.just(byteBuf))
.then()
.toFuture();
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 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.
@@ -535,6 +535,7 @@ public class SimpAnnotationMethodMessageHandlerTests {
@Controller
@MessageMapping("listenable-future")
@SuppressWarnings("deprecation")
private static class ListenableFutureController {
private ListenableFutureTask<String> future;

View File

@@ -20,6 +20,7 @@ import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicReference;
@@ -45,7 +46,6 @@ import org.springframework.messaging.tcp.TcpConnection;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.concurrent.SettableListenableFuture;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -90,9 +90,9 @@ public class DefaultStompSessionTests {
new CompositeMessageConverter(
Arrays.asList(new StringMessageConverter(), new ByteArrayMessageConverter())));
SettableListenableFuture<Void> future = new SettableListenableFuture<>();
future.set(null);
given(this.connection.send(this.messageCaptor.capture())).willReturn(future);
CompletableFuture<Void> future = new CompletableFuture<>();
future.complete(null);
given(this.connection.sendAsync(this.messageCaptor.capture())).willReturn(future);
}
@@ -177,7 +177,7 @@ public class DefaultStompSessionTests {
@Test
public void heartbeatNotSupportedByServer() {
this.session.afterConnected(this.connection);
verify(this.connection).send(any());
verify(this.connection).sendAsync(any());
this.connectHeaders.setHeartbeat(new long[] {10000, 10000});
@@ -193,7 +193,7 @@ public class DefaultStompSessionTests {
@Test
public void heartbeatTasks() {
this.session.afterConnected(this.connection);
verify(this.connection).send(any());
verify(this.connection).sendAsync(any());
this.connectHeaders.setHeartbeat(new long[] {10000, 10000});
@@ -216,7 +216,7 @@ public class DefaultStompSessionTests {
writeTask.run();
StompHeaderAccessor accessor = StompHeaderAccessor.createForHeartbeat();
Message<byte[]> message = MessageBuilder.createMessage(new byte[] {'\n'}, accessor.getMessageHeaders());
verify(this.connection).send(eq(message));
verify(this.connection).sendAsync(eq(message));
verifyNoMoreInteractions(this.connection);
reset(this.sessionHandler);
@@ -435,10 +435,9 @@ public class DefaultStompSessionTests {
assertThat(this.session.isConnected()).isTrue();
IllegalStateException exception = new IllegalStateException("simulated exception");
SettableListenableFuture<Void> future = new SettableListenableFuture<>();
future.setException(exception);
CompletableFuture<Void> future = CompletableFuture.failedFuture(exception);
given(this.connection.send(any())).willReturn(future);
given(this.connection.sendAsync(any())).willReturn(future);
assertThatExceptionOfType(MessageDeliveryException.class).isThrownBy(() ->
this.session.send("/topic/foo", "sample payload".getBytes(StandardCharsets.UTF_8)))
.withCause(exception);

View File

@@ -21,6 +21,7 @@ import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -38,7 +39,6 @@ import org.springframework.messaging.converter.StringMessageConverter;
import org.springframework.messaging.simp.stomp.StompSession.Subscription;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import static org.assertj.core.api.Assertions.assertThat;
@@ -107,10 +107,10 @@ public class ReactorNettyTcpStompClientTests {
public void publishSubscribe() throws Exception {
String destination = "/topic/foo";
ConsumingHandler consumingHandler1 = new ConsumingHandler(destination);
ListenableFuture<StompSession> consumerFuture1 = this.client.connect(consumingHandler1);
CompletableFuture<StompSession> consumerFuture1 = this.client.connectAsync(consumingHandler1);
ConsumingHandler consumingHandler2 = new ConsumingHandler(destination);
ListenableFuture<StompSession> consumerFuture2 = this.client.connect(consumingHandler2);
CompletableFuture<StompSession> consumerFuture2 = this.client.connectAsync(consumingHandler2);
assertThat(consumingHandler1.awaitForSubscriptions(5000)).isTrue();
assertThat(consumingHandler2.awaitForSubscriptions(5000)).isTrue();
@@ -118,7 +118,7 @@ public class ReactorNettyTcpStompClientTests {
ProducingHandler producingHandler = new ProducingHandler();
producingHandler.addToSend(destination, "foo1");
producingHandler.addToSend(destination, "foo2");
ListenableFuture<StompSession> producerFuture = this.client.connect(producingHandler);
CompletableFuture<StompSession> producerFuture = this.client.connectAsync(producingHandler);
assertThat(consumingHandler1.awaitForMessageCount(2, 5000)).isTrue();
assertThat(consumingHandler1.getReceived()).containsExactly("foo1", "foo2");

View File

@@ -20,6 +20,7 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -39,8 +40,6 @@ import org.springframework.messaging.tcp.TcpConnection;
import org.springframework.messaging.tcp.TcpConnectionHandler;
import org.springframework.messaging.tcp.TcpOperations;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureTask;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
@@ -309,10 +308,8 @@ class StompBrokerRelayMessageHandlerTests {
}
private static ListenableFutureTask<Void> getVoidFuture() {
ListenableFutureTask<Void> futureTask = new ListenableFutureTask<>(() -> null);
futureTask.run();
return futureTask;
private static CompletableFuture<Void> getVoidFuture() {
return CompletableFuture.completedFuture(null);
}
@@ -336,21 +333,22 @@ class StompBrokerRelayMessageHandlerTests {
}
@Override
public ListenableFuture<Void> connect(TcpConnectionHandler<byte[]> handler) {
public CompletableFuture<Void> connectAsync(TcpConnectionHandler<byte[]> handler) {
this.connectionHandler = handler;
handler.afterConnected(this.connection);
return getVoidFuture();
}
@Override
public ListenableFuture<Void> connect(TcpConnectionHandler<byte[]> handler, ReconnectStrategy strategy) {
public CompletableFuture<Void> connectAsync(TcpConnectionHandler<byte[]> handler,
ReconnectStrategy reconnectStrategy) {
this.connectionHandler = handler;
handler.afterConnected(this.connection);
return getVoidFuture();
}
@Override
public ListenableFuture<Void> shutdown() {
public CompletableFuture<Void> shutdownAsync() {
return getVoidFuture();
}
@@ -371,7 +369,7 @@ class StompBrokerRelayMessageHandlerTests {
}
@Override
public ListenableFuture<Void> send(Message<byte[]> message) {
public CompletableFuture<Void> sendAsync(Message<byte[]> message) {
this.messages.add(message);
return getVoidFuture();
}