Skip to content

Commit 5330c52

Browse files
committed
Merge branch cbeams/SPR-7022
* SPR-7022: Support initial delay attribute for scheduled tasks Polish scheduled task execution infrastructure
2 parents e2fbaa8 + 53673d6 commit 5330c52

File tree

18 files changed

+758
-186
lines changed

18 files changed

+758
-186
lines changed

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

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 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,11 +24,11 @@
2424

2525
/**
2626
* Annotation that marks a method to be scheduled. Exactly one of the
27-
* <code>cron</code>, <code>fixedDelay</code>, or <code>fixedRate</code>
28-
* attributes must be provided.
27+
* {@link #cron()}, {@link #fixedDelay()}, or {@link #fixedRate()}
28+
* attributes must be specified.
2929
*
3030
* <p>The annotated method must expect no arguments and have a
31-
* <code>void</code> return type.
31+
* {@code void} return type.
3232
*
3333
* <p>Processing of {@code @Scheduled} annotations is performed by
3434
* registering a {@link ScheduledAnnotationBeanPostProcessor}. This can be
@@ -37,6 +37,7 @@
3737
*
3838
* @author Mark Fisher
3939
* @author Dave Syer
40+
* @author Chris Beams
4041
* @since 3.0
4142
* @see EnableScheduling
4243
* @see ScheduledAnnotationBeanPostProcessor
@@ -49,9 +50,10 @@
4950
/**
5051
* A cron-like expression, extending the usual UN*X definition to include
5152
* triggers on the second as well as minute, hour, day of month, month
52-
* and day of week. e.g. <code>"0 * * * * MON-FRI"</code> means once
53-
* per minute on weekdays (at the top of the minute - the 0th second).
53+
* and day of week. e.g. {@code "0 * * * * MON-FRI"} means once per minute on
54+
* weekdays (at the top of the minute - the 0th second).
5455
* @return an expression that can be parsed to a cron schedule
56+
* @see org.springframework.scheduling.support.CronSequenceGenerator
5557
*/
5658
String cron() default "";
5759

@@ -68,4 +70,12 @@
6870
*/
6971
long fixedRate() default -1;
7072

73+
/**
74+
* Number of milliseconds to delay before the first execution of a
75+
* {@link #fixedRate()} or {@link #fixedDelay()} task.
76+
* @return the initial delay in milliseconds
77+
* @since 3.2
78+
*/
79+
long initialDelay() default 0;
80+
7181
}

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

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -17,6 +17,7 @@
1717
package org.springframework.scheduling.annotation;
1818

1919
import java.lang.reflect.Method;
20+
2021
import java.util.HashMap;
2122
import java.util.Map;
2223
import java.util.concurrent.ScheduledExecutorService;
@@ -33,6 +34,8 @@
3334
import org.springframework.core.annotation.AnnotationUtils;
3435
import org.springframework.scheduling.TaskScheduler;
3536
import org.springframework.scheduling.Trigger;
37+
import org.springframework.scheduling.config.CronTask;
38+
import org.springframework.scheduling.config.IntervalTask;
3639
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
3740
import org.springframework.scheduling.support.ScheduledMethodRunnable;
3841
import org.springframework.util.Assert;
@@ -74,13 +77,7 @@ public class ScheduledAnnotationBeanPostProcessor
7477

7578
private ApplicationContext applicationContext;
7679

77-
private ScheduledTaskRegistrar registrar;
78-
79-
private final Map<Runnable, String> cronTasks = new HashMap<Runnable, String>();
80-
81-
private final Map<Runnable, Long> fixedDelayTasks = new HashMap<Runnable, Long>();
82-
83-
private final Map<Runnable, Long> fixedRateTasks = new HashMap<Runnable, Long>();
80+
private final ScheduledTaskRegistrar registrar = new ScheduledTaskRegistrar();
8481

8582

8683
/**
@@ -104,7 +101,6 @@ public int getOrder() {
104101
return LOWEST_PRECEDENCE;
105102
}
106103

107-
108104
public Object postProcessBeforeInitialization(Object bean, String beanName) {
109105
return bean;
110106
}
@@ -124,9 +120,11 @@ public void doWith(Method method) throws IllegalArgumentException, IllegalAccess
124120
// found a @Scheduled method on the target class for this JDK proxy -> is it
125121
// also present on the proxy itself?
126122
method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
127-
} catch (SecurityException ex) {
123+
}
124+
catch (SecurityException ex) {
128125
ReflectionUtils.handleReflectionException(ex);
129-
} catch (NoSuchMethodException ex) {
126+
}
127+
catch (NoSuchMethodException ex) {
130128
throw new IllegalStateException(String.format(
131129
"@Scheduled method '%s' found on bean target class '%s', " +
132130
"but not found in any interface(s) for bean JDK proxy. Either " +
@@ -137,26 +135,27 @@ public void doWith(Method method) throws IllegalArgumentException, IllegalAccess
137135
}
138136
Runnable runnable = new ScheduledMethodRunnable(bean, method);
139137
boolean processedSchedule = false;
140-
String errorMessage = "Exactly one of 'cron', 'fixedDelay', or 'fixedRate' is required.";
138+
String errorMessage = "Exactly one of the 'cron', 'fixedDelay', or 'fixedRate' attributes is required.";
141139
String cron = annotation.cron();
142140
if (!"".equals(cron)) {
143141
processedSchedule = true;
144142
if (embeddedValueResolver != null) {
145143
cron = embeddedValueResolver.resolveStringValue(cron);
146144
}
147-
cronTasks.put(runnable, cron);
145+
registrar.addCronTask(new CronTask(runnable, cron));
148146
}
147+
long initialDelay = annotation.initialDelay();
149148
long fixedDelay = annotation.fixedDelay();
150149
if (fixedDelay >= 0) {
151150
Assert.isTrue(!processedSchedule, errorMessage);
152151
processedSchedule = true;
153-
fixedDelayTasks.put(runnable, fixedDelay);
152+
registrar.addFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay));
154153
}
155154
long fixedRate = annotation.fixedRate();
156155
if (fixedRate >= 0) {
157156
Assert.isTrue(!processedSchedule, errorMessage);
158157
processedSchedule = true;
159-
fixedRateTasks.put(runnable, fixedRate);
158+
registrar.addFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay));
160159
}
161160
Assert.isTrue(processedSchedule, errorMessage);
162161
}
@@ -170,17 +169,8 @@ public void onApplicationEvent(ContextRefreshedEvent event) {
170169
return;
171170
}
172171

173-
Map<String, SchedulingConfigurer> configurers = applicationContext.getBeansOfType(SchedulingConfigurer.class);
174-
175-
if (this.cronTasks.isEmpty() && this.fixedDelayTasks.isEmpty() &&
176-
this.fixedRateTasks.isEmpty() && configurers.isEmpty()) {
177-
return;
178-
}
179-
180-
this.registrar = new ScheduledTaskRegistrar();
181-
this.registrar.setCronTasks(this.cronTasks);
182-
this.registrar.setFixedDelayTasks(this.fixedDelayTasks);
183-
this.registrar.setFixedRateTasks(this.fixedRateTasks);
172+
Map<String, SchedulingConfigurer> configurers =
173+
this.applicationContext.getBeansOfType(SchedulingConfigurer.class);
184174

185175
if (this.scheduler != null) {
186176
this.registrar.setScheduler(this.scheduler);
@@ -190,19 +180,23 @@ public void onApplicationEvent(ContextRefreshedEvent event) {
190180
configurer.configureTasks(this.registrar);
191181
}
192182

193-
if (registrar.getScheduler() == null) {
183+
if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
194184
Map<String, ? super Object> schedulers = new HashMap<String, Object>();
195185
schedulers.putAll(applicationContext.getBeansOfType(TaskScheduler.class));
196186
schedulers.putAll(applicationContext.getBeansOfType(ScheduledExecutorService.class));
197187
if (schedulers.size() == 0) {
198188
// do nothing -> fall back to default scheduler
199-
} else if (schedulers.size() == 1) {
189+
}
190+
else if (schedulers.size() == 1) {
200191
this.registrar.setScheduler(schedulers.values().iterator().next());
201-
} else if (schedulers.size() >= 2){
202-
throw new IllegalStateException("More than one TaskScheduler and/or ScheduledExecutorService " +
203-
"exist within the context. Remove all but one of the beans; or implement the " +
204-
"SchedulingConfigurer interface and call ScheduledTaskRegistrar#setScheduler " +
205-
"explicitly within the configureTasks() callback. Found the following beans: " + schedulers.keySet());
192+
}
193+
else if (schedulers.size() >= 2){
194+
throw new IllegalStateException(
195+
"More than one TaskScheduler and/or ScheduledExecutorService " +
196+
"exist within the context. Remove all but one of the beans; or " +
197+
"implement the SchedulingConfigurer interface and call " +
198+
"ScheduledTaskRegistrar#setScheduler explicitly within the " +
199+
"configureTasks() callback. Found the following beans: " + schedulers.keySet());
206200
}
207201
}
208202

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

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -19,12 +19,15 @@
1919
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
2020

2121
/**
22-
* Interface to be implemented by @{@link org.springframework.context.annotation.Configuration}
23-
* classes annotated with @{@link EnableScheduling} that wish to register scheduled tasks
24-
* in a <em>programmatic</em> fashion as opposed to the <em>declarative</em> approach of
25-
* using the @{@link Scheduled} annotation. For example, this may be necessary when
26-
* implementing {@link org.springframework.scheduling.Trigger Trigger}-based tasks, which
27-
* are not supported by the {@code @Scheduled} annotation.
22+
* Optional interface to be implemented by @{@link
23+
* org.springframework.context.annotation.Configuration Configuration} classes annotated
24+
* with @{@link EnableScheduling}. Typically used for setting a specific
25+
* {@link org.springframework.scheduling.TaskScheduler TaskScheduler} bean to be used when
26+
* executing scheduled tasks or for registering scheduled tasks in a <em>programmatic</em>
27+
* fashion as opposed to the <em>declarative</em> approach of using the @{@link Scheduled}
28+
* annotation. For example, this may be necessary when implementing {@link
29+
* org.springframework.scheduling.Trigger Trigger}-based tasks, which are not supported by
30+
* the {@code @Scheduled} annotation.
2831
*
2932
* <p>See @{@link EnableScheduling} for detailed usage examples.
3033
*
@@ -35,6 +38,12 @@
3538
*/
3639
public interface SchedulingConfigurer {
3740

41+
/**
42+
* Callback allowing a {@link org.springframework.scheduling.TaskScheduler
43+
* TaskScheduler} and specific {@link org.springframework.scheduling.config.Task Task}
44+
* instances to be registered against the given the {@link ScheduledTaskRegistrar}
45+
* @param taskRegistrar the registrar to be configured.
46+
*/
3847
void configureTasks(ScheduledTaskRegistrar taskRegistrar);
3948

4049
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2002-2012 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.scheduling.config;
18+
19+
import org.springframework.scheduling.annotation.Scheduled;
20+
import org.springframework.scheduling.support.CronTrigger;
21+
22+
/**
23+
* {@link TriggerTask} implementation defining a {@code Runnable} to be executed according
24+
* to a {@linkplain org.springframework.scheduling.support.CronSequenceGenerator standard
25+
* cron expression}.
26+
*
27+
* @author Chris Beams
28+
* @since 3.2
29+
* @see Scheduled#cron()
30+
* @see ScheduledTaskRegistrar#setCronTasksList(java.util.List)
31+
* @see org.springframework.scheduling.TaskScheduler
32+
*/
33+
public class CronTask extends TriggerTask {
34+
35+
private String expression;
36+
37+
38+
/**
39+
* Create a new {@code CronTask}.
40+
* @param runnable the underlying task to execute
41+
* @param expression cron expression defining when the task should be executed
42+
*/
43+
public CronTask(Runnable runnable, String expression) {
44+
this(runnable, new CronTrigger(expression));
45+
}
46+
47+
/**
48+
* Create a new {@code CronTask}.
49+
* @param runnable the underlying task to execute
50+
* @param cronTrigger the cron trigger defining when the task should be executed
51+
*/
52+
public CronTask(Runnable runnable, CronTrigger cronTrigger) {
53+
super(runnable, cronTrigger);
54+
this.expression = cronTrigger.getExpression();
55+
}
56+
57+
public String getExpression() {
58+
return expression;
59+
}
60+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2002-2012 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.scheduling.config;
18+
19+
/**
20+
* {@link Task} implementation defining a {@code Runnable} to be executed at a given
21+
* millisecond interval which may be treated as fixed-rate or fixed-delay depending on
22+
* context.
23+
*
24+
* @author Chris Beams
25+
* @since 3.2
26+
* @see org.springframework.scheduling.annotation.Scheduled#fixedRate()
27+
* @see org.springframework.scheduling.annotation.Scheduled#fixedDelay()
28+
* @see ScheduledTaskRegistrar#setFixedRateTasksList(java.util.List)
29+
* @see ScheduledTaskRegistrar#setFixedDelayTasksList(java.util.List)
30+
* @see org.springframework.scheduling.TaskScheduler
31+
*/
32+
public class IntervalTask extends Task {
33+
34+
private final long interval;
35+
36+
private final long initialDelay;
37+
38+
39+
/**
40+
* Create a new {@code IntervalTask}.
41+
* @param runnable the underlying task to execute
42+
* @param interval how often in milliseconds the task should be executed
43+
* @param initialDelay initial delay before first execution of the task
44+
*/
45+
public IntervalTask(Runnable runnable, long interval, long initialDelay) {
46+
super(runnable);
47+
this.initialDelay = initialDelay;
48+
this.interval = interval;
49+
}
50+
51+
/**
52+
* Create a new {@code IntervalTask} with no initial delay.
53+
* @param runnable the underlying task to execute
54+
* @param interval how often in milliseconds the task should be executed
55+
*/
56+
public IntervalTask(Runnable runnable, long interval) {
57+
this(runnable, interval, 0);
58+
}
59+
60+
61+
public long getInterval() {
62+
return interval;
63+
}
64+
65+
public long getInitialDelay() {
66+
return initialDelay;
67+
}
68+
}

0 commit comments

Comments
 (0)