Skip to content

Commit e9b0a19

Browse files
committed
Document TaskDecorator usage with TaskExecutors
Closes gh-33438
1 parent d1920c0 commit e9b0a19

File tree

6 files changed

+120
-4
lines changed

6 files changed

+120
-4
lines changed

framework-docs/modules/ROOT/pages/integration/scheduling.adoc

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ The variants that Spring provides are as follows:
5050
for each invocation. However, it does support a concurrency limit that blocks
5151
any invocations that are over the limit until a slot has been freed up. If you
5252
are looking for true pooling, see `ThreadPoolTaskExecutor`, later in this list.
53+
This will use JDK 21's Virtual Threads, when the "virtualThreads"
54+
option is enabled. This implementation also supports graceful shutdown through
55+
Spring's lifecycle management.
5356
* `ConcurrentTaskExecutor`:
5457
This implementation is an adapter for a `java.util.concurrent.Executor` instance.
5558
There is an alternative (`ThreadPoolTaskExecutor`) that exposes the `Executor`
@@ -61,15 +64,13 @@ The variants that Spring provides are as follows:
6164
a `java.util.concurrent.ThreadPoolExecutor` and wraps it in a `TaskExecutor`.
6265
If you need to adapt to a different kind of `java.util.concurrent.Executor`,
6366
we recommend that you use a `ConcurrentTaskExecutor` instead.
67+
It also provides a pause/resume capability and graceful shutdown through
68+
Spring's lifecycle management.
6469
* `DefaultManagedTaskExecutor`:
6570
This implementation uses a JNDI-obtained `ManagedExecutorService` in a JSR-236
6671
compatible runtime environment (such as a Jakarta EE application server),
6772
replacing a CommonJ WorkManager for that purpose.
6873

69-
As of 6.1, `ThreadPoolTaskExecutor` provides a pause/resume capability and graceful
70-
shutdown through Spring's lifecycle management. There is also a new "virtualThreads"
71-
option on `SimpleAsyncTaskExecutor` which is aligned with JDK 21's Virtual Threads,
72-
as well as a graceful shutdown capability for `SimpleAsyncTaskExecutor` as well.
7374

7475

7576
[[scheduling-task-executor-usage]]
@@ -89,6 +90,22 @@ To configure the rules that the `TaskExecutor` uses, we expose simple bean prope
8990

9091
include-code::./TaskExecutorConfiguration[tag=snippet,indent=0]
9192

93+
Most `TaskExecutor` implementations provide a way to automatically wrap tasks submitted
94+
with a `TaskDecorator`. Decorators should delegate to the task it is wrapping, possibly
95+
implementing custom behavior before/after the execution of the task.
96+
97+
Let's consider a simple implementation that will log messages before and after the execution
98+
or our tasks:
99+
100+
include-code::./LoggingTaskDecorator[indent=0]
101+
102+
We can then configure our decorator on a `TaskExecutor` instance:
103+
104+
include-code::./TaskExecutorConfiguration[tag=decorator,indent=0]
105+
106+
In case multiple decorators are needed, the `org.springframework.core.task.support.CompositeTaskDecorator`
107+
can be used to execute sequentially multiple decorators.
108+
92109

93110
[[scheduling-task-scheduler]]
94111
== The Spring `TaskScheduler` Abstraction
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.docs.integration.schedulingtaskexecutorusage;
18+
19+
import org.apache.commons.logging.Log;
20+
import org.apache.commons.logging.LogFactory;
21+
22+
import org.springframework.core.task.TaskDecorator;
23+
24+
public class LoggingTaskDecorator implements TaskDecorator {
25+
26+
private static final Log logger = LogFactory.getLog(LoggingTaskDecorator.class);
27+
28+
@Override
29+
public Runnable decorate(Runnable runnable) {
30+
return () -> {
31+
logger.debug("Before execution of " + runnable);
32+
runnable.run();
33+
logger.debug("After execution of " + runnable);
34+
};
35+
}
36+
}

framework-docs/src/main/java/org/springframework/docs/integration/schedulingtaskexecutorusage/TaskExecutorConfiguration.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,13 @@ TaskExecutorExample taskExecutorExample(ThreadPoolTaskExecutor taskExecutor) {
3838
return new TaskExecutorExample(taskExecutor);
3939
}
4040
// end::snippet[]
41+
42+
// tag::decorator[]
43+
@Bean
44+
ThreadPoolTaskExecutor decoratedTaskExecutor() {
45+
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
46+
taskExecutor.setTaskDecorator(new LoggingTaskDecorator());
47+
return taskExecutor;
48+
}
49+
// end::decorator[]
4150
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.docs.integration.schedulingtaskexecutorusage
18+
19+
import org.apache.commons.logging.Log
20+
import org.apache.commons.logging.LogFactory
21+
import org.springframework.core.task.TaskDecorator
22+
23+
class LoggingTaskDecorator : TaskDecorator {
24+
25+
override fun decorate(runnable: Runnable): Runnable {
26+
return Runnable {
27+
logger.debug("Before execution of $runnable")
28+
runnable.run()
29+
logger.debug("After execution of $runnable")
30+
}
31+
}
32+
33+
companion object {
34+
private val logger: Log = LogFactory.getLog(
35+
LoggingTaskDecorator::class.java
36+
)
37+
}
38+
}

framework-docs/src/main/kotlin/org/springframework/docs/integration/schedulingtaskexecutorusage/TaskExecutorConfiguration.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,11 @@ class TaskExecutorConfiguration {
3434
@Bean
3535
fun taskExecutorExample(taskExecutor: ThreadPoolTaskExecutor) = TaskExecutorExample(taskExecutor)
3636
// end::snippet[]
37+
38+
// tag::decorator[]
39+
@Bean
40+
fun decoratedTaskExecutor() = ThreadPoolTaskExecutor().apply {
41+
setTaskDecorator(LoggingTaskDecorator())
42+
}
43+
// end::decorator[]
3744
}

framework-docs/src/main/resources/org/springframework/docs/integration/schedulingtaskexecutorusage/TaskExecutorConfiguration.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,13 @@
1515
</bean>
1616
<!-- end::snippet[] -->
1717

18+
<!-- tag::decorator[] -->
19+
<bean id="decoratedTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
20+
<property name="taskDecorator" ref="loggingTaskDecorator"/>
21+
</bean>
22+
<!-- end::decorator[] -->
23+
24+
<bean id="loggingTaskDecorator" class="org.springframework.docs.integration.schedulingtaskexecutorusage.LoggingTaskDecorator">
25+
</bean>
26+
1827
</beans>

0 commit comments

Comments
 (0)