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-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.
@@ -27,7 +27,11 @@ import org.springframework.util.concurrent.ListenableFuture;
* @author Arjen Poutsma
* @since 4.0
* @see ListenableFuture
* @deprecated as of 6.0, in favor of
* {@link AsyncTaskExecutor#submitCompletable(Runnable)} and
* {@link AsyncTaskExecutor#submitCompletable(Callable)}
*/
@Deprecated
public interface AsyncListenableTaskExecutor extends AsyncTaskExecutor {
/**
@@ -36,7 +40,9 @@ public interface AsyncListenableTaskExecutor extends AsyncTaskExecutor {
* @param task the {@code Runnable} to execute (never {@code null})
* @return a {@code ListenableFuture} representing pending completion of the task
* @throws TaskRejectedException if the given task was not accepted
* @deprecated in favor of {@link AsyncTaskExecutor#submitCompletable(Runnable)}
*/
@Deprecated
ListenableFuture<?> submitListenable(Runnable task);
/**
@@ -46,7 +52,9 @@ public interface AsyncListenableTaskExecutor extends AsyncTaskExecutor {
* @param task the {@code Callable} to execute (never {@code null})
* @return a {@code ListenableFuture} representing pending completion of the task
* @throws TaskRejectedException if the given task was not accepted
* @deprecated in favor of {@link AsyncTaskExecutor#submitCompletable(Callable)}
*/
@Deprecated
<T> ListenableFuture<T> submitListenable(Callable<T> task);
}

View File

@@ -17,8 +17,11 @@
package org.springframework.core.task;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import org.springframework.util.concurrent.FutureUtils;
/**
* Extended interface for asynchronous {@link TaskExecutor} implementations,
* offering support for {@link java.util.concurrent.Callable}.
@@ -91,4 +94,29 @@ public interface AsyncTaskExecutor extends TaskExecutor {
*/
<T> Future<T> submit(Callable<T> task);
/**
* Submit a {@code Runnable} task for execution, receiving a {@code CompletableFuture}
* representing that task. The Future will return a {@code null} result upon completion.
* @param task the {@code Runnable} to execute (never {@code null})
* @return a {@code CompletableFuture} representing pending completion of the task
* @throws TaskRejectedException if the given task was not accepted
* @since 6.0
*/
default CompletableFuture<Void> submitCompletable(Runnable task) {
return CompletableFuture.runAsync(task, this);
}
/**
* Submit a {@code Callable} task for execution, receiving a {@code CompletableFuture}
* representing that task. The Future will return the Callable's result upon
* completion.
* @param task the {@code Callable} to execute (never {@code null})
* @return a {@code CompletableFuture} representing pending completion of the task
* @throws TaskRejectedException if the given task was not accepted
* @since 6.0
*/
default <T> CompletableFuture<T> submitCompletable(Callable<T> task) {
return FutureUtils.callAsync(task, this);
}
}

View File

@@ -46,7 +46,7 @@ import org.springframework.util.concurrent.ListenableFutureTask;
* @see SyncTaskExecutor
* @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
*/
@SuppressWarnings("serial")
@SuppressWarnings({"serial", "deprecation"})
public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
implements AsyncListenableTaskExecutor, Serializable {

View File

@@ -43,6 +43,7 @@ import org.springframework.util.concurrent.ListenableFutureTask;
* @see java.util.concurrent.ExecutorService
* @see java.util.concurrent.Executors
*/
@SuppressWarnings("deprecation")
public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
private final Executor concurrentExecutor;

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.
@@ -30,7 +30,9 @@ import java.util.concurrent.TimeoutException;
* @author Juergen Hoeller
* @since 4.2
* @param <T> the result type returned by this Future's {@code get} method
* @deprecated as of 6.0, with no concrete replacement
*/
@Deprecated
public class CompletableToListenableFutureAdapter<T> implements ListenableFuture<T> {
private final CompletableFuture<T> completableFuture;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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,12 +16,17 @@
package org.springframework.util.concurrent;
import java.util.function.BiConsumer;
/**
* Failure callback for a {@link ListenableFuture}.
*
* @author Sebastien Deleuze
* @since 4.1
* @deprecated as of 6.0, in favor of
* {@link java.util.concurrent.CompletableFuture#whenComplete(BiConsumer)}
*/
@Deprecated
@FunctionalInterface
public interface FailureCallback {

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.
@@ -33,7 +33,9 @@ import org.springframework.util.Assert;
* @since 4.0
* @param <T> the type of this {@code Future}
* @param <S> the type of the adaptee's {@code Future}
* @deprecated as of 6.0, with no concrete replacement
*/
@Deprecated
public abstract class FutureAdapter<T, S> implements Future<T> {
private final Future<S> adaptee;

View File

@@ -0,0 +1,82 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://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.util.concurrent;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import java.util.function.Supplier;
import org.springframework.util.Assert;
/**
* Convenience utilities for working with {@link java.util.concurrent.Future}
* and implementations.
*
* @author Arjen Poutsma
* @since 6.0
*/
public abstract class FutureUtils {
/**
* Return a new {@code CompletableFuture} that is asynchronously completed
* by a task running in the {@link ForkJoinPool#commonPool()} with
* the value obtained by calling the given {@code Callable}.
* @param callable a function that returns the value to be used, or throws
* an exception
* @return the new CompletableFuture
*/
public static <T> CompletableFuture<T> callAsync(Callable<T> callable) {
Assert.notNull(callable, "Callable must not be null");
CompletableFuture<T> result = new CompletableFuture<>();
return result.completeAsync(toSupplier(callable, result));
}
/**
* Return a new {@code CompletableFuture} that is asynchronously completed
* by a task running in the given executor with the value obtained
* by calling the given Supplier.
* @param callable a function that returns the value to be used, or throws
* an exception
* @param executor the executor to use for asynchronous execution
* @return the new CompletableFuture
*/
public static <T> CompletableFuture<T> callAsync(Callable<T> callable, Executor executor) {
Assert.notNull(callable, "Callable must not be null");
Assert.notNull(executor, "Executor must not be null");
CompletableFuture<T> result = new CompletableFuture<>();
return result.completeAsync(toSupplier(callable, result), executor);
}
private static <T> Supplier<T> toSupplier(Callable<T> callable, CompletableFuture<T> result) {
return () -> {
try {
return callable.call();
}
catch (Exception ex) {
// wrap the exception just like CompletableFuture::supplyAsync does
result.completeExceptionally((ex instanceof CompletionException) ? ex : new CompletionException(ex));
return null;
}
};
}
}

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.
@@ -18,6 +18,7 @@ package org.springframework.util.concurrent;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.function.BiConsumer;
/**
* Extend {@link Future} with the capability to accept completion callbacks.
@@ -31,13 +32,18 @@ import java.util.concurrent.Future;
* @author Juergen Hoeller
* @since 4.0
* @param <T> the result type returned by this Future's {@code get} method
* @deprecated as of 6.0, in favor of {@link CompletableFuture}
*/
@Deprecated
public interface ListenableFuture<T> extends Future<T> {
/**
* Register the given {@code ListenableFutureCallback}.
* @param callback the callback to register
* @deprecated as of 6.0, in favor of
* {@link CompletableFuture#whenComplete(BiConsumer)}
*/
@Deprecated
void addCallback(ListenableFutureCallback<? super T> callback);
/**
@@ -45,7 +51,10 @@ public interface ListenableFuture<T> extends Future<T> {
* @param successCallback the success callback
* @param failureCallback the failure callback
* @since 4.1
* @deprecated as of 6.0, in favor of
* {@link CompletableFuture#whenComplete(BiConsumer)}
*/
@Deprecated
void addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback);

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.
@@ -31,7 +31,10 @@ import org.springframework.lang.Nullable;
* @since 4.0
* @param <T> the type of this {@code Future}
* @param <S> the type of the adaptee's {@code Future}
* @deprecated as of 6.0, in favor of
* {@link java.util.concurrent.CompletableFuture}
*/
@Deprecated
public abstract class ListenableFutureAdapter<T, S> extends FutureAdapter<T, S> implements ListenableFuture<T> {
/**

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,6 +16,8 @@
package org.springframework.util.concurrent;
import java.util.function.BiConsumer;
/**
* Callback mechanism for the outcome, success or failure, from a
* {@link ListenableFuture}.
@@ -24,7 +26,10 @@ package org.springframework.util.concurrent;
* @author Sebastien Deleuze
* @since 4.0
* @param <T> the result type
* @deprecated as of 6.0, in favor of
* {@link java.util.concurrent.CompletableFuture#whenComplete(BiConsumer)}
*/
@Deprecated
public interface ListenableFutureCallback<T> extends SuccessCallback<T>, FailureCallback {
}

View File

@@ -33,7 +33,9 @@ import org.springframework.util.Assert;
* @author Rossen Stoyanchev
* @since 4.0
* @param <T> the callback result type
* @deprecated as of 6.0, with no concrete replacement
*/
@Deprecated
public class ListenableFutureCallbackRegistry<T> {
private final Queue<SuccessCallback<? super T>> successCallbacks = new ArrayDeque<>(1);

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.
@@ -29,7 +29,9 @@ import org.springframework.lang.Nullable;
* @author Arjen Poutsma
* @since 4.0
* @param <T> the result type returned by this Future's {@code get} method
* @deprecated as of 6.0, with no concrete replacement
*/
@Deprecated
public class ListenableFutureTask<T> extends FutureTask<T> implements ListenableFuture<T> {
private final ListenableFutureCallbackRegistry<T> callbacks = new ListenableFutureCallbackRegistry<>();

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.
@@ -27,7 +27,9 @@ import reactor.core.publisher.Mono;
* @author Stephane Maldini
* @since 5.1
* @param <T> the object type
* @deprecated as of 6.0, in favor of {@link Mono#toFuture()}
*/
@Deprecated
public class MonoToListenableFutureAdapter<T> extends CompletableToListenableFutureAdapter<T> {
public MonoToListenableFutureAdapter(Mono<T> mono) {

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.
@@ -36,7 +36,9 @@ import org.springframework.util.Assert;
* @author Juergen Hoeller
* @since 4.1
* @param <T> the result type returned by this Future's {@code get} method
* @deprecated as of 6.0, in favor of {@link CompletableFuture}
*/
@Deprecated
public class SettableListenableFuture<T> implements ListenableFuture<T> {
private static final Callable<Object> DUMMY_CALLABLE = () -> {

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,6 +16,8 @@
package org.springframework.util.concurrent;
import java.util.function.BiConsumer;
import org.springframework.lang.Nullable;
/**
@@ -24,7 +26,10 @@ import org.springframework.lang.Nullable;
* @author Sebastien Deleuze
* @since 4.1
* @param <T> the result type
* @deprecated as of 6.0, in favor of
* {@link java.util.concurrent.CompletableFuture#whenComplete(BiConsumer)}
*/
@Deprecated
@FunctionalInterface
public interface SuccessCallback<T> {

View File

@@ -0,0 +1,116 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://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.util.concurrent;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import org.junit.jupiter.api.Test;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Arjen Poutsma
*/
class FutureUtilsTests {
@Test
void callAsyncNormal() throws ExecutionException, InterruptedException {
String foo = "Foo";
CompletableFuture<String> future = FutureUtils.callAsync(() -> foo);
assertThat(future.get()).isEqualTo(foo);
assertThat(future.isCancelled()).isFalse();
assertThat(future.isDone()).isTrue();
CountDownLatch latch = new CountDownLatch(1);
future.whenComplete((s, throwable) -> {
assertThat(s).isEqualTo(foo);
assertThat(throwable).isNull();
latch.countDown();
});
latch.await();
}
@Test
void callAsyncException() throws InterruptedException {
RuntimeException ex = new RuntimeException("Foo");
CompletableFuture<String> future = FutureUtils.callAsync(() -> {
throw ex;
});
assertThatExceptionOfType(ExecutionException.class)
.isThrownBy(future::get)
.withCause(ex);
assertThat(future.isCancelled()).isFalse();
assertThat(future.isDone()).isTrue();
CountDownLatch latch = new CountDownLatch(1);
future.whenComplete((s, throwable) -> {
assertThat(s).isNull();
assertThat(throwable).isInstanceOf(CompletionException.class)
.hasCause(ex);
latch.countDown();
});
latch.await();
}
@Test
void callAsyncNormalExecutor() throws ExecutionException, InterruptedException {
String foo = "Foo";
CompletableFuture<String> future = FutureUtils.callAsync(() -> foo, new SimpleAsyncTaskExecutor());
assertThat(future.get()).isEqualTo(foo);
assertThat(future.isCancelled()).isFalse();
assertThat(future.isDone()).isTrue();
CountDownLatch latch = new CountDownLatch(1);
future.whenComplete((s, throwable) -> {
assertThat(s).isEqualTo(foo);
assertThat(throwable).isNull();
latch.countDown();
});
latch.await();
}
@Test
void callAsyncExceptionExecutor() throws InterruptedException {
RuntimeException ex = new RuntimeException("Foo");
CompletableFuture<String> future = FutureUtils.callAsync(() -> {
throw ex;
}, new SimpleAsyncTaskExecutor());
assertThatExceptionOfType(ExecutionException.class)
.isThrownBy(future::get)
.withCause(ex);
assertThat(future.isCancelled()).isFalse();
assertThat(future.isDone()).isTrue();
CountDownLatch latch = new CountDownLatch(1);
future.whenComplete((s, throwable) -> {
assertThat(s).isNull();
assertThat(throwable).isInstanceOf(CompletionException.class)
.hasCause(ex);
latch.countDown();
});
latch.await();
}
}