Skip to content

Commit 25be5e0

Browse files
committed
TaskDecorator callback supported by common TaskExecutor implementations
Issue: SPR-13930
1 parent ac44f9e commit 25be5e0

File tree

7 files changed

+204
-20
lines changed

7 files changed

+204
-20
lines changed

spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java

+19-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -31,6 +31,7 @@
3131

3232
import org.springframework.beans.factory.InitializingBean;
3333
import org.springframework.core.task.AsyncListenableTaskExecutor;
34+
import org.springframework.core.task.TaskDecorator;
3435
import org.springframework.core.task.TaskRejectedException;
3536
import org.springframework.jndi.JndiLocatorSupport;
3637
import org.springframework.scheduling.SchedulingException;
@@ -71,6 +72,8 @@ public class WorkManagerTaskExecutor extends JndiLocatorSupport
7172

7273
private WorkListener workListener;
7374

75+
private TaskDecorator taskDecorator;
76+
7477

7578
/**
7679
* Specify the CommonJ WorkManager to delegate to.
@@ -101,6 +104,20 @@ public void setWorkListener(WorkListener workListener) {
101104
this.workListener = workListener;
102105
}
103106

107+
/**
108+
* Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable}
109+
* about to be executed.
110+
* <p>Note that such a decorator is not necessarily being applied to the
111+
* user-supplied {@code Runnable}/{@code Callable} but rather to the actual
112+
* execution callback (which may be a wrapper around the user-supplied task).
113+
* <p>The primary use case is to set some execution context around the task's
114+
* invocation, or to provide some monitoring/statistics for task execution.
115+
* @since 4.3
116+
*/
117+
public void setTaskDecorator(TaskDecorator taskDecorator) {
118+
this.taskDecorator = taskDecorator;
119+
}
120+
104121
@Override
105122
public void afterPropertiesSet() throws NamingException {
106123
if (this.workManager == null) {
@@ -119,7 +136,7 @@ public void afterPropertiesSet() throws NamingException {
119136
@Override
120137
public void execute(Runnable task) {
121138
Assert.state(this.workManager != null, "No WorkManager specified");
122-
Work work = new DelegatingWork(task);
139+
Work work = new DelegatingWork(this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
123140
try {
124141
if (this.workListener != null) {
125142
this.workManager.schedule(work, this.workListener);

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

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -26,6 +26,7 @@
2626
import javax.enterprise.concurrent.ManagedTask;
2727

2828
import org.springframework.core.task.AsyncListenableTaskExecutor;
29+
import org.springframework.core.task.TaskDecorator;
2930
import org.springframework.core.task.support.TaskExecutorAdapter;
3031
import org.springframework.scheduling.SchedulingAwareRunnable;
3132
import org.springframework.scheduling.SchedulingTaskExecutor;
@@ -127,6 +128,20 @@ public final Executor getConcurrentExecutor() {
127128
return this.concurrentExecutor;
128129
}
129130

131+
/**
132+
* Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable}
133+
* about to be executed.
134+
* <p>Note that such a decorator is not necessarily being applied to the
135+
* user-supplied {@code Runnable}/{@code Callable} but rather to the actual
136+
* execution callback (which may be a wrapper around the user-supplied task).
137+
* <p>The primary use case is to set some execution context around the task's
138+
* invocation, or to provide some monitoring/statistics for task execution.
139+
* @since 4.3
140+
*/
141+
public final void setTaskDecorator(TaskDecorator taskDecorator) {
142+
this.adaptedExecutor.setTaskDecorator(taskDecorator);
143+
}
144+
130145

131146
@Override
132147
public void execute(Runnable task) {

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

+43-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2016 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,6 +30,7 @@
3030
import java.util.concurrent.TimeUnit;
3131

3232
import org.springframework.core.task.AsyncListenableTaskExecutor;
33+
import org.springframework.core.task.TaskDecorator;
3334
import org.springframework.core.task.TaskRejectedException;
3435
import org.springframework.scheduling.SchedulingTaskExecutor;
3536
import org.springframework.util.Assert;
@@ -82,6 +83,8 @@ public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
8283

8384
private boolean allowCoreThreadTimeOut = false;
8485

86+
private TaskDecorator taskDecorator;
87+
8588
private ThreadPoolExecutor threadPoolExecutor;
8689

8790

@@ -177,15 +180,51 @@ public void setAllowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) {
177180
this.allowCoreThreadTimeOut = allowCoreThreadTimeOut;
178181
}
179182

183+
/**
184+
* Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable}
185+
* about to be executed.
186+
* <p>Note that such a decorator is not necessarily being applied to the
187+
* user-supplied {@code Runnable}/{@code Callable} but rather to the actual
188+
* execution callback (which may be a wrapper around the user-supplied task).
189+
* <p>The primary use case is to set some execution context around the task's
190+
* invocation, or to provide some monitoring/statistics for task execution.
191+
* @since 4.3
192+
*/
193+
public void setTaskDecorator(TaskDecorator taskDecorator) {
194+
this.taskDecorator = taskDecorator;
195+
}
180196

197+
198+
/**
199+
* Note: This method exposes an {@link ExecutorService} to its base class
200+
* but stores the actual {@link ThreadPoolExecutor} handle internally.
201+
* Do not override this method for replacing the executor, rather just for
202+
* decorating its {@code ExecutorService} handle or storing custom state.
203+
*/
181204
@Override
182205
protected ExecutorService initializeExecutor(
183206
ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
184207

185208
BlockingQueue<Runnable> queue = createQueue(this.queueCapacity);
186-
ThreadPoolExecutor executor = new ThreadPoolExecutor(
187-
this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
188-
queue, threadFactory, rejectedExecutionHandler);
209+
210+
ThreadPoolExecutor executor;
211+
if (this.taskDecorator != null) {
212+
executor = new ThreadPoolExecutor(
213+
this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
214+
queue, threadFactory, rejectedExecutionHandler) {
215+
@Override
216+
public void execute(Runnable command) {
217+
super.execute(taskDecorator.decorate(command));
218+
}
219+
};
220+
}
221+
else {
222+
executor = new ThreadPoolExecutor(
223+
this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
224+
queue, threadFactory, rejectedExecutionHandler);
225+
226+
}
227+
189228
if (this.allowCoreThreadTimeOut) {
190229
executor.allowCoreThreadTimeOut(true);
191230
}

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

+20-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -65,6 +65,8 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implement
6565

6666
private ThreadFactory threadFactory;
6767

68+
private TaskDecorator taskDecorator;
69+
6870

6971
/**
7072
* Create a new SimpleAsyncTaskExecutor with default thread name prefix.
@@ -109,6 +111,20 @@ public final ThreadFactory getThreadFactory() {
109111
return this.threadFactory;
110112
}
111113

114+
/**
115+
* Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable}
116+
* about to be executed.
117+
* <p>Note that such a decorator is not necessarily being applied to the
118+
* user-supplied {@code Runnable}/{@code Callable} but rather to the actual
119+
* execution callback (which may be a wrapper around the user-supplied task).
120+
* <p>The primary use case is to set some execution context around the task's
121+
* invocation, or to provide some monitoring/statistics for task execution.
122+
* @since 4.3
123+
*/
124+
public final void setTaskDecorator(TaskDecorator taskDecorator) {
125+
this.taskDecorator = taskDecorator;
126+
}
127+
112128
/**
113129
* Set the maximum number of parallel accesses allowed.
114130
* -1 indicates no concurrency limit at all.
@@ -163,12 +179,13 @@ public void execute(Runnable task) {
163179
@Override
164180
public void execute(Runnable task, long startTimeout) {
165181
Assert.notNull(task, "Runnable must not be null");
182+
Runnable taskToUse = (this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
166183
if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
167184
this.concurrencyThrottle.beforeAccess();
168-
doExecute(new ConcurrencyThrottlingRunnable(task));
185+
doExecute(new ConcurrencyThrottlingRunnable(taskToUse));
169186
}
170187
else {
171-
doExecute(task);
188+
doExecute(taskToUse);
172189
}
173190
}
174191

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2002-2016 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+
* http://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.core.task;
18+
19+
/**
20+
* A callback interface for a decorator to be applied to any {@link Runnable}
21+
* about to be executed.
22+
*
23+
* <p>Note that such a decorator is not necessarily being applied to the
24+
* user-supplied {@code Runnable}/{@code Callable} but rather to the actual
25+
* execution callback (which may be a wrapper around the user-supplied task).
26+
*
27+
* <p>The primary use case is to set some execution context around the task's
28+
* invocation, or to provide some monitoring/statistics for task execution.
29+
*
30+
* @author Juergen Hoeller
31+
* @since 4.3
32+
* @see TaskExecutor#execute(Runnable)
33+
* @see SimpleAsyncTaskExecutor#setTaskDecorator
34+
*/
35+
public interface TaskDecorator {
36+
37+
/**
38+
* Decorate the given {@code Runnable}, returning a potentially wrapped
39+
* {@code Runnable} for actual execution.
40+
* @param runnable the original {@code Runnable}
41+
* @return the decorated {@code Runnable}
42+
*/
43+
Runnable decorate(Runnable runnable);
44+
45+
}

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

+42-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -24,6 +24,7 @@
2424
import java.util.concurrent.RejectedExecutionException;
2525

2626
import org.springframework.core.task.AsyncListenableTaskExecutor;
27+
import org.springframework.core.task.TaskDecorator;
2728
import org.springframework.core.task.TaskRejectedException;
2829
import org.springframework.util.Assert;
2930
import org.springframework.util.concurrent.ListenableFuture;
@@ -45,6 +46,8 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
4546

4647
private final Executor concurrentExecutor;
4748

49+
private TaskDecorator taskDecorator;
50+
4851

4952
/**
5053
* Create a new TaskExecutorAdapter,
@@ -57,14 +60,29 @@ public TaskExecutorAdapter(Executor concurrentExecutor) {
5760
}
5861

5962

63+
/**
64+
* Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable}
65+
* about to be executed.
66+
* <p>Note that such a decorator is not necessarily being applied to the
67+
* user-supplied {@code Runnable}/{@code Callable} but rather to the actual
68+
* execution callback (which may be a wrapper around the user-supplied task).
69+
* <p>The primary use case is to set some execution context around the task's
70+
* invocation, or to provide some monitoring/statistics for task execution.
71+
* @since 4.3
72+
*/
73+
public final void setTaskDecorator(TaskDecorator taskDecorator) {
74+
this.taskDecorator = taskDecorator;
75+
}
76+
77+
6078
/**
6179
* Delegates to the specified JDK concurrent executor.
6280
* @see java.util.concurrent.Executor#execute(Runnable)
6381
*/
6482
@Override
6583
public void execute(Runnable task) {
6684
try {
67-
this.concurrentExecutor.execute(task);
85+
doExecute(this.concurrentExecutor, this.taskDecorator, task);
6886
}
6987
catch (RejectedExecutionException ex) {
7088
throw new TaskRejectedException(
@@ -80,12 +98,12 @@ public void execute(Runnable task, long startTimeout) {
8098
@Override
8199
public Future<?> submit(Runnable task) {
82100
try {
83-
if (this.concurrentExecutor instanceof ExecutorService) {
101+
if (this.taskDecorator == null && this.concurrentExecutor instanceof ExecutorService) {
84102
return ((ExecutorService) this.concurrentExecutor).submit(task);
85103
}
86104
else {
87105
FutureTask<Object> future = new FutureTask<Object>(task, null);
88-
this.concurrentExecutor.execute(future);
106+
doExecute(this.concurrentExecutor, this.taskDecorator, future);
89107
return future;
90108
}
91109
}
@@ -98,12 +116,12 @@ public Future<?> submit(Runnable task) {
98116
@Override
99117
public <T> Future<T> submit(Callable<T> task) {
100118
try {
101-
if (this.concurrentExecutor instanceof ExecutorService) {
119+
if (this.taskDecorator == null && this.concurrentExecutor instanceof ExecutorService) {
102120
return ((ExecutorService) this.concurrentExecutor).submit(task);
103121
}
104122
else {
105123
FutureTask<T> future = new FutureTask<T>(task);
106-
this.concurrentExecutor.execute(future);
124+
doExecute(this.concurrentExecutor, this.taskDecorator, future);
107125
return future;
108126
}
109127
}
@@ -117,7 +135,7 @@ public <T> Future<T> submit(Callable<T> task) {
117135
public ListenableFuture<?> submitListenable(Runnable task) {
118136
try {
119137
ListenableFutureTask<Object> future = new ListenableFutureTask<Object>(task, null);
120-
this.concurrentExecutor.execute(future);
138+
doExecute(this.concurrentExecutor, this.taskDecorator, future);
121139
return future;
122140
}
123141
catch (RejectedExecutionException ex) {
@@ -130,7 +148,7 @@ public ListenableFuture<?> submitListenable(Runnable task) {
130148
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
131149
try {
132150
ListenableFutureTask<T> future = new ListenableFutureTask<T>(task);
133-
this.concurrentExecutor.execute(future);
151+
doExecute(this.concurrentExecutor, this.taskDecorator, future);
134152
return future;
135153
}
136154
catch (RejectedExecutionException ex) {
@@ -139,4 +157,20 @@ public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
139157
}
140158
}
141159

160+
161+
/**
162+
* Actually execute the given {@code Runnable} (which may be a user-supplied task
163+
* or a wrapper around a user-supplied task) with the given executor.
164+
* @param concurrentExecutor the underlying JDK concurrent executor to delegate to
165+
* @param taskDecorator the specified decorator to be applied, if any
166+
* @param runnable the runnable to execute
167+
* @throws RejectedExecutionException if the given runnable cannot be accepted
168+
* @since 4.3
169+
*/
170+
protected void doExecute(Executor concurrentExecutor, TaskDecorator taskDecorator, Runnable runnable)
171+
throws RejectedExecutionException{
172+
173+
concurrentExecutor.execute(taskDecorator != null ? taskDecorator.decorate(runnable) : runnable);
174+
}
175+
142176
}

0 commit comments

Comments
 (0)