Skip to content

Commit 2aa74c9

Browse files
committed
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
1 parent 735051b commit 2aa74c9

File tree

74 files changed

+1148
-380
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+1148
-380
lines changed

spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.util.Map;
2121
import java.util.concurrent.Callable;
2222
import java.util.concurrent.CompletableFuture;
23-
import java.util.concurrent.CompletionException;
2423
import java.util.concurrent.ConcurrentHashMap;
2524
import java.util.concurrent.Executor;
2625
import java.util.concurrent.Future;
@@ -158,7 +157,6 @@ public void setBeanFactory(BeanFactory beanFactory) {
158157

159158
/**
160159
* Determine the specific executor to use when executing the given method.
161-
* <p>Should preferably return an {@link AsyncListenableTaskExecutor} implementation.
162160
* @return the executor to use (or {@code null}, but just if no default executor is available)
163161
*/
164162
@Nullable
@@ -176,8 +174,8 @@ protected AsyncTaskExecutor determineAsyncExecutor(Method method) {
176174
if (targetExecutor == null) {
177175
return null;
178176
}
179-
executor = (targetExecutor instanceof AsyncListenableTaskExecutor ?
180-
(AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));
177+
executor = (targetExecutor instanceof AsyncTaskExecutor ?
178+
(AsyncTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));
181179
this.executors.put(method, executor);
182180
}
183181
return executor;
@@ -276,17 +274,11 @@ protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
276274
* @param returnType the declared return type (potentially a {@link Future} variant)
277275
* @return the execution result (potentially a corresponding {@link Future} handle)
278276
*/
277+
@SuppressWarnings("deprecation")
279278
@Nullable
280279
protected Object doSubmit(Callable<Object> task, AsyncTaskExecutor executor, Class<?> returnType) {
281280
if (CompletableFuture.class.isAssignableFrom(returnType)) {
282-
return CompletableFuture.supplyAsync(() -> {
283-
try {
284-
return task.call();
285-
}
286-
catch (Throwable ex) {
287-
throw new CompletionException(ex);
288-
}
289-
}, executor);
281+
return executor.submitCompletable(task);
290282
}
291283
else if (ListenableFuture.class.isAssignableFrom(returnType)) {
292284
return ((AsyncListenableTaskExecutor) executor).submitListenable(task);

spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
* @see org.springframework.core.task.TaskExecutor
4848
* @see SchedulerFactoryBean#setTaskExecutor
4949
*/
50+
@SuppressWarnings("deprecation")
5051
public class SimpleThreadPoolTaskExecutor extends SimpleThreadPool
5152
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, InitializingBean, DisposableBean {
5253

spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -262,6 +262,7 @@ protected Object[] resolveArguments(ApplicationEvent event) {
262262
return new Object[] {event};
263263
}
264264

265+
@SuppressWarnings("deprecation")
265266
protected void handleResult(Object result) {
266267
if (reactiveStreamsPresent && new ReactiveResultHandler().subscribeToPublisher(result)) {
267268
if (logger.isTraceEnabled()) {

spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -45,7 +45,9 @@
4545
* @see Async
4646
* @see #forValue(Object)
4747
* @see #forExecutionException(Throwable)
48+
* @deprecated as of 6.0, in favor of {@link CompletableFuture}
4849
*/
50+
@Deprecated
4951
public class AsyncResult<V> implements ListenableFuture<V> {
5052

5153
@Nullable

spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
* @see DefaultManagedTaskExecutor
6363
* @see ThreadPoolTaskExecutor
6464
*/
65+
@SuppressWarnings("deprecation")
6566
public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, SchedulingTaskExecutor {
6667

6768
@Nullable

spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
* @see ThreadPoolExecutorFactoryBean
8181
* @see ConcurrentTaskExecutor
8282
*/
83-
@SuppressWarnings("serial")
83+
@SuppressWarnings({"serial", "deprecation"})
8484
public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
8585
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor {
8686

spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
* @see #setThreadFactory
6060
* @see #setErrorHandler
6161
*/
62-
@SuppressWarnings("serial")
62+
@SuppressWarnings({"serial", "deprecation"})
6363
public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
6464
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, TaskScheduler {
6565

spring-context/src/test/java/org/springframework/scheduling/concurrent/AbstractSchedulingTaskExecutorTests.java

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -18,10 +18,13 @@
1818

1919
import java.util.concurrent.Callable;
2020
import java.util.concurrent.CancellationException;
21+
import java.util.concurrent.CompletableFuture;
22+
import java.util.concurrent.CompletionException;
2123
import java.util.concurrent.CountDownLatch;
2224
import java.util.concurrent.ExecutionException;
2325
import java.util.concurrent.Future;
2426
import java.util.concurrent.TimeUnit;
27+
import java.util.concurrent.TimeoutException;
2528
import java.util.concurrent.atomic.AtomicInteger;
2629
import java.util.concurrent.atomic.AtomicReference;
2730

@@ -33,6 +36,7 @@
3336

3437
import org.springframework.beans.factory.DisposableBean;
3538
import org.springframework.core.task.AsyncListenableTaskExecutor;
39+
import org.springframework.lang.Nullable;
3640
import org.springframework.util.concurrent.ListenableFuture;
3741

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

142+
@Test
143+
void submitCompletableRunnable() throws Exception {
144+
TestTask task = new TestTask(this.testName, 1);
145+
// Act
146+
CompletableFuture<Void> future = executor.submitCompletable(task);
147+
future.whenComplete(this::storeOutcome);
148+
// Assert
149+
Awaitility.await()
150+
.atMost(1, TimeUnit.SECONDS)
151+
.pollInterval(10, TimeUnit.MILLISECONDS)
152+
.until(future::isDone);
153+
assertThat(outcome).isNull();
154+
assertThreadNamePrefix(task);
155+
}
156+
138157
@Test
139158
void submitFailingListenableRunnable() throws Exception {
140159
TestTask task = new TestTask(this.testName, 0);
@@ -149,6 +168,20 @@ void submitFailingListenableRunnable() throws Exception {
149168
assertThat(outcome.getClass()).isSameAs(RuntimeException.class);
150169
}
151170

171+
@Test
172+
void submitFailingCompletableRunnable() throws Exception {
173+
TestTask task = new TestTask(this.testName, 0);
174+
CompletableFuture<?> future = executor.submitCompletable(task);
175+
future.whenComplete(this::storeOutcome);
176+
177+
Awaitility.await()
178+
.dontCatchUncaughtExceptions()
179+
.atMost(1, TimeUnit.SECONDS)
180+
.pollInterval(10, TimeUnit.MILLISECONDS)
181+
.until(() -> future.isDone() && outcome != null);
182+
assertThat(outcome.getClass()).isSameAs(CompletionException.class);
183+
}
184+
152185
@Test
153186
void submitListenableRunnableWithGetAfterShutdown() throws Exception {
154187
ListenableFuture<?> future1 = executor.submitListenable(new TestTask(this.testName, -1));
@@ -169,6 +202,26 @@ void submitListenableRunnableWithGetAfterShutdown() throws Exception {
169202
future2.get(1000, TimeUnit.MILLISECONDS)));
170203
}
171204

205+
@Test
206+
void submitCompletableRunnableWithGetAfterShutdown() throws Exception {
207+
CompletableFuture<?> future1 = executor.submitCompletable(new TestTask(this.testName, -1));
208+
CompletableFuture<?> future2 = executor.submitCompletable(new TestTask(this.testName, -1));
209+
shutdownExecutor();
210+
211+
try {
212+
future1.get(1000, TimeUnit.MILLISECONDS);
213+
}
214+
catch (Exception ex) {
215+
/* ignore */
216+
}
217+
Awaitility.await()
218+
.atMost(4, TimeUnit.SECONDS)
219+
.pollInterval(10, TimeUnit.MILLISECONDS)
220+
.untilAsserted(() ->
221+
assertThatExceptionOfType(TimeoutException.class).isThrownBy(() ->
222+
future2.get(1000, TimeUnit.MILLISECONDS)));
223+
}
224+
172225
@Test
173226
void submitCallable() throws Exception {
174227
TestCallable task = new TestCallable(this.testName, 1);
@@ -246,6 +299,57 @@ void submitListenableCallableWithGetAfterShutdown() throws Exception {
246299
});
247300
}
248301

302+
@Test
303+
void submitCompletableCallable() throws Exception {
304+
TestCallable task = new TestCallable(this.testName, 1);
305+
// Act
306+
CompletableFuture<String> future = this.executor.submitCompletable(task);
307+
future.whenComplete(this::storeOutcome);
308+
// Assert
309+
Awaitility.await()
310+
.atMost(1, TimeUnit.SECONDS)
311+
.pollInterval(10, TimeUnit.MILLISECONDS)
312+
.until(() -> future.isDone() && outcome != null);
313+
assertThat(outcome.toString().substring(0, this.threadNamePrefix.length())).isEqualTo(this.threadNamePrefix);
314+
}
315+
316+
@Test
317+
void submitFailingCompletableCallable() throws Exception {
318+
TestCallable task = new TestCallable(this.testName, 0);
319+
// Act
320+
CompletableFuture<String> future = this.executor.submitCompletable(task);
321+
future.whenComplete(this::storeOutcome);
322+
// Assert
323+
Awaitility.await()
324+
.dontCatchUncaughtExceptions()
325+
.atMost(1, TimeUnit.SECONDS)
326+
.pollInterval(10, TimeUnit.MILLISECONDS)
327+
.until(() -> future.isDone() && outcome != null);
328+
assertThat(outcome.getClass()).isSameAs(CompletionException.class);
329+
}
330+
331+
@Test
332+
void submitCompletableCallableWithGetAfterShutdown() throws Exception {
333+
CompletableFuture<?> future1 = executor.submitCompletable(new TestCallable(this.testName, -1));
334+
CompletableFuture<?> future2 = executor.submitCompletable(new TestCallable(this.testName, -1));
335+
shutdownExecutor();
336+
assertThatExceptionOfType(TimeoutException.class).isThrownBy(() -> {
337+
future1.get(1000, TimeUnit.MILLISECONDS);
338+
future2.get(1000, TimeUnit.MILLISECONDS);
339+
});
340+
}
341+
342+
343+
private void storeOutcome(@Nullable Object o, @Nullable Throwable t) {
344+
if (o != null) {
345+
this.outcome = o;
346+
}
347+
else if (t != null) {
348+
this.outcome = t;
349+
}
350+
}
351+
352+
249353

250354
protected void assertThreadNamePrefix(TestTask task) {
251355
assertThat(task.lastThread.getName().substring(0, this.threadNamePrefix.length())).isEqualTo(this.threadNamePrefix);

spring-core/src/main/java/org/springframework/core/task/AsyncListenableTaskExecutor.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -27,7 +27,11 @@
2727
* @author Arjen Poutsma
2828
* @since 4.0
2929
* @see ListenableFuture
30+
* @deprecated as of 6.0, in favor of
31+
* {@link AsyncTaskExecutor#submitCompletable(Runnable)} and
32+
* {@link AsyncTaskExecutor#submitCompletable(Callable)}
3033
*/
34+
@Deprecated
3135
public interface AsyncListenableTaskExecutor extends AsyncTaskExecutor {
3236

3337
/**
@@ -36,7 +40,9 @@ public interface AsyncListenableTaskExecutor extends AsyncTaskExecutor {
3640
* @param task the {@code Runnable} to execute (never {@code null})
3741
* @return a {@code ListenableFuture} representing pending completion of the task
3842
* @throws TaskRejectedException if the given task was not accepted
43+
* @deprecated in favor of {@link AsyncTaskExecutor#submitCompletable(Runnable)}
3944
*/
45+
@Deprecated
4046
ListenableFuture<?> submitListenable(Runnable task);
4147

4248
/**
@@ -46,7 +52,9 @@ public interface AsyncListenableTaskExecutor extends AsyncTaskExecutor {
4652
* @param task the {@code Callable} to execute (never {@code null})
4753
* @return a {@code ListenableFuture} representing pending completion of the task
4854
* @throws TaskRejectedException if the given task was not accepted
55+
* @deprecated in favor of {@link AsyncTaskExecutor#submitCompletable(Callable)}
4956
*/
57+
@Deprecated
5058
<T> ListenableFuture<T> submitListenable(Callable<T> task);
5159

5260
}

spring-core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
package org.springframework.core.task;
1818

1919
import java.util.concurrent.Callable;
20+
import java.util.concurrent.CompletableFuture;
2021
import java.util.concurrent.Future;
2122

23+
import org.springframework.util.concurrent.FutureUtils;
24+
2225
/**
2326
* Extended interface for asynchronous {@link TaskExecutor} implementations,
2427
* offering support for {@link java.util.concurrent.Callable}.
@@ -91,4 +94,29 @@ public interface AsyncTaskExecutor extends TaskExecutor {
9194
*/
9295
<T> Future<T> submit(Callable<T> task);
9396

97+
/**
98+
* Submit a {@code Runnable} task for execution, receiving a {@code CompletableFuture}
99+
* representing that task. The Future will return a {@code null} result upon completion.
100+
* @param task the {@code Runnable} to execute (never {@code null})
101+
* @return a {@code CompletableFuture} representing pending completion of the task
102+
* @throws TaskRejectedException if the given task was not accepted
103+
* @since 6.0
104+
*/
105+
default CompletableFuture<Void> submitCompletable(Runnable task) {
106+
return CompletableFuture.runAsync(task, this);
107+
}
108+
109+
/**
110+
* Submit a {@code Callable} task for execution, receiving a {@code CompletableFuture}
111+
* representing that task. The Future will return the Callable's result upon
112+
* completion.
113+
* @param task the {@code Callable} to execute (never {@code null})
114+
* @return a {@code CompletableFuture} representing pending completion of the task
115+
* @throws TaskRejectedException if the given task was not accepted
116+
* @since 6.0
117+
*/
118+
default <T> CompletableFuture<T> submitCompletable(Callable<T> task) {
119+
return FutureUtils.callAsync(task, this);
120+
}
121+
94122
}

spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
* @see SyncTaskExecutor
4747
* @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
4848
*/
49-
@SuppressWarnings("serial")
49+
@SuppressWarnings({"serial", "deprecation"})
5050
public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
5151
implements AsyncListenableTaskExecutor, Serializable {
5252

spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
* @see java.util.concurrent.ExecutorService
4444
* @see java.util.concurrent.Executors
4545
*/
46+
@SuppressWarnings("deprecation")
4647
public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
4748

4849
private final Executor concurrentExecutor;

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -30,7 +30,9 @@
3030
* @author Juergen Hoeller
3131
* @since 4.2
3232
* @param <T> the result type returned by this Future's {@code get} method
33+
* @deprecated as of 6.0, with no concrete replacement
3334
*/
35+
@Deprecated
3436
public class CompletableToListenableFutureAdapter<T> implements ListenableFuture<T> {
3537

3638
private final CompletableFuture<T> completableFuture;

0 commit comments

Comments
 (0)