Skip to content

Commit

Permalink
Deprecate ListenableFuture in favor of CompletableFuture
Browse files Browse the repository at this point in the history
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
  • Loading branch information
poutsma committed Jul 27, 2022
1 parent 735051b commit 2aa74c9
Show file tree
Hide file tree
Showing 74 changed files with 1,148 additions and 380 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
Expand Down Expand Up @@ -158,7 +157,6 @@ public void setBeanFactory(BeanFactory beanFactory) {

/**
* Determine the specific executor to use when executing the given method.
* <p>Should preferably return an {@link AsyncListenableTaskExecutor} implementation.
* @return the executor to use (or {@code null}, but just if no default executor is available)
*/
@Nullable
Expand All @@ -176,8 +174,8 @@ protected AsyncTaskExecutor determineAsyncExecutor(Method method) {
if (targetExecutor == null) {
return null;
}
executor = (targetExecutor instanceof AsyncListenableTaskExecutor ?
(AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));
executor = (targetExecutor instanceof AsyncTaskExecutor ?
(AsyncTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));
this.executors.put(method, executor);
}
return executor;
Expand Down Expand Up @@ -276,17 +274,11 @@ protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
* @param returnType the declared return type (potentially a {@link Future} variant)
* @return the execution result (potentially a corresponding {@link Future} handle)
*/
@SuppressWarnings("deprecation")
@Nullable
protected Object doSubmit(Callable<Object> task, AsyncTaskExecutor executor, Class<?> returnType) {
if (CompletableFuture.class.isAssignableFrom(returnType)) {
return CompletableFuture.supplyAsync(() -> {
try {
return task.call();
}
catch (Throwable ex) {
throw new CompletionException(ex);
}
}, executor);
return executor.submitCompletable(task);
}
else if (ListenableFuture.class.isAssignableFrom(returnType)) {
return ((AsyncListenableTaskExecutor) executor).submitListenable(task);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
* @see org.springframework.core.task.TaskExecutor
* @see SchedulerFactoryBean#setTaskExecutor
*/
@SuppressWarnings("deprecation")
public class SimpleThreadPoolTaskExecutor extends SimpleThreadPool
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, InitializingBean, DisposableBean {

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -262,6 +262,7 @@ protected Object[] resolveArguments(ApplicationEvent event) {
return new Object[] {event};
}

@SuppressWarnings("deprecation")
protected void handleResult(Object result) {
if (reactiveStreamsPresent && new ReactiveResultHandler().subscribeToPublisher(result)) {
if (logger.isTraceEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -45,7 +45,9 @@
* @see Async
* @see #forValue(Object)
* @see #forExecutionException(Throwable)
* @deprecated as of 6.0, in favor of {@link CompletableFuture}
*/
@Deprecated
public class AsyncResult<V> implements ListenableFuture<V> {

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
* @see DefaultManagedTaskExecutor
* @see ThreadPoolTaskExecutor
*/
@SuppressWarnings("deprecation")
public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, SchedulingTaskExecutor {

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
* @see ThreadPoolExecutorFactoryBean
* @see ConcurrentTaskExecutor
*/
@SuppressWarnings("serial")
@SuppressWarnings({"serial", "deprecation"})
public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
* @see #setThreadFactory
* @see #setErrorHandler
*/
@SuppressWarnings("serial")
@SuppressWarnings({"serial", "deprecation"})
public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, TaskScheduler {

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -18,10 +18,13 @@

import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

Expand All @@ -33,6 +36,7 @@

import org.springframework.beans.factory.DisposableBean;
import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.lang.Nullable;
import org.springframework.util.concurrent.ListenableFuture;

import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -135,6 +139,21 @@ void submitListenableRunnable() throws Exception {
assertThreadNamePrefix(task);
}

@Test
void submitCompletableRunnable() throws Exception {
TestTask task = new TestTask(this.testName, 1);
// Act
CompletableFuture<Void> future = executor.submitCompletable(task);
future.whenComplete(this::storeOutcome);
// Assert
Awaitility.await()
.atMost(1, TimeUnit.SECONDS)
.pollInterval(10, TimeUnit.MILLISECONDS)
.until(future::isDone);
assertThat(outcome).isNull();
assertThreadNamePrefix(task);
}

@Test
void submitFailingListenableRunnable() throws Exception {
TestTask task = new TestTask(this.testName, 0);
Expand All @@ -149,6 +168,20 @@ void submitFailingListenableRunnable() throws Exception {
assertThat(outcome.getClass()).isSameAs(RuntimeException.class);
}

@Test
void submitFailingCompletableRunnable() throws Exception {
TestTask task = new TestTask(this.testName, 0);
CompletableFuture<?> future = executor.submitCompletable(task);
future.whenComplete(this::storeOutcome);

Awaitility.await()
.dontCatchUncaughtExceptions()
.atMost(1, TimeUnit.SECONDS)
.pollInterval(10, TimeUnit.MILLISECONDS)
.until(() -> future.isDone() && outcome != null);
assertThat(outcome.getClass()).isSameAs(CompletionException.class);
}

@Test
void submitListenableRunnableWithGetAfterShutdown() throws Exception {
ListenableFuture<?> future1 = executor.submitListenable(new TestTask(this.testName, -1));
Expand All @@ -169,6 +202,26 @@ void submitListenableRunnableWithGetAfterShutdown() throws Exception {
future2.get(1000, TimeUnit.MILLISECONDS)));
}

@Test
void submitCompletableRunnableWithGetAfterShutdown() throws Exception {
CompletableFuture<?> future1 = executor.submitCompletable(new TestTask(this.testName, -1));
CompletableFuture<?> future2 = executor.submitCompletable(new TestTask(this.testName, -1));
shutdownExecutor();

try {
future1.get(1000, TimeUnit.MILLISECONDS);
}
catch (Exception ex) {
/* ignore */
}
Awaitility.await()
.atMost(4, TimeUnit.SECONDS)
.pollInterval(10, TimeUnit.MILLISECONDS)
.untilAsserted(() ->
assertThatExceptionOfType(TimeoutException.class).isThrownBy(() ->
future2.get(1000, TimeUnit.MILLISECONDS)));
}

@Test
void submitCallable() throws Exception {
TestCallable task = new TestCallable(this.testName, 1);
Expand Down Expand Up @@ -246,6 +299,57 @@ void submitListenableCallableWithGetAfterShutdown() throws Exception {
});
}

@Test
void submitCompletableCallable() throws Exception {
TestCallable task = new TestCallable(this.testName, 1);
// Act
CompletableFuture<String> future = this.executor.submitCompletable(task);
future.whenComplete(this::storeOutcome);
// Assert
Awaitility.await()
.atMost(1, TimeUnit.SECONDS)
.pollInterval(10, TimeUnit.MILLISECONDS)
.until(() -> future.isDone() && outcome != null);
assertThat(outcome.toString().substring(0, this.threadNamePrefix.length())).isEqualTo(this.threadNamePrefix);
}

@Test
void submitFailingCompletableCallable() throws Exception {
TestCallable task = new TestCallable(this.testName, 0);
// Act
CompletableFuture<String> future = this.executor.submitCompletable(task);
future.whenComplete(this::storeOutcome);
// Assert
Awaitility.await()
.dontCatchUncaughtExceptions()
.atMost(1, TimeUnit.SECONDS)
.pollInterval(10, TimeUnit.MILLISECONDS)
.until(() -> future.isDone() && outcome != null);
assertThat(outcome.getClass()).isSameAs(CompletionException.class);
}

@Test
void submitCompletableCallableWithGetAfterShutdown() throws Exception {
CompletableFuture<?> future1 = executor.submitCompletable(new TestCallable(this.testName, -1));
CompletableFuture<?> future2 = executor.submitCompletable(new TestCallable(this.testName, -1));
shutdownExecutor();
assertThatExceptionOfType(TimeoutException.class).isThrownBy(() -> {
future1.get(1000, TimeUnit.MILLISECONDS);
future2.get(1000, TimeUnit.MILLISECONDS);
});
}


private void storeOutcome(@Nullable Object o, @Nullable Throwable t) {
if (o != null) {
this.outcome = o;
}
else if (t != null) {
this.outcome = t;
}
}



protected void assertThreadNamePrefix(TestTask task) {
assertThat(task.lastThread.getName().substring(0, this.threadNamePrefix.length())).isEqualTo(this.threadNamePrefix);
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -27,7 +27,11 @@
* @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 {

/**
Expand All @@ -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);

/**
Expand All @@ -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);

}
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand Down Expand Up @@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
* @see SyncTaskExecutor
* @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
*/
@SuppressWarnings("serial")
@SuppressWarnings({"serial", "deprecation"})
public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
implements AsyncListenableTaskExecutor, Serializable {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
* @see java.util.concurrent.ExecutorService
* @see java.util.concurrent.Executors
*/
@SuppressWarnings("deprecation")
public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {

private final Executor concurrentExecutor;
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -30,7 +30,9 @@
* @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;
Expand Down
Loading

0 comments on commit 2aa74c9

Please sign in to comment.