diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
index ccda008fcc8..dea788ce088 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -24,11 +24,11 @@ import java.lang.annotation.Target;
/**
* Annotation that marks a method to be scheduled. Exactly one of the
- * cron, fixedDelay, or fixedRate
- * attributes must be provided.
+ * {@link #cron()}, {@link #fixedDelay()}, or {@link #fixedRate()}
+ * attributes must be specified.
*
*
The annotated method must expect no arguments and have a
- * void return type.
+ * {@code void} return type.
*
*
Processing of {@code @Scheduled} annotations is performed by
* registering a {@link ScheduledAnnotationBeanPostProcessor}. This can be
@@ -37,6 +37,7 @@ import java.lang.annotation.Target;
*
* @author Mark Fisher
* @author Dave Syer
+ * @author Chris Beams
* @since 3.0
* @see EnableScheduling
* @see ScheduledAnnotationBeanPostProcessor
@@ -49,9 +50,10 @@ public @interface Scheduled {
/**
* A cron-like expression, extending the usual UN*X definition to include
* triggers on the second as well as minute, hour, day of month, month
- * and day of week. e.g. "0 * * * * MON-FRI" means once
- * per minute on weekdays (at the top of the minute - the 0th second).
+ * and day of week. e.g. {@code "0 * * * * MON-FRI"} means once per minute on
+ * weekdays (at the top of the minute - the 0th second).
* @return an expression that can be parsed to a cron schedule
+ * @see org.springframework.scheduling.support.CronSequenceGenerator
*/
String cron() default "";
@@ -68,4 +70,12 @@ public @interface Scheduled {
*/
long fixedRate() default -1;
+ /**
+ * Number of milliseconds to delay before the first execution of a
+ * {@link #fixedRate()} or {@link #fixedDelay()} task.
+ * @return the initial delay in milliseconds
+ * @since 3.2
+ */
+ long initialDelay() default 0;
+
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
index 692270044ce..f5f236d2f9d 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -17,6 +17,7 @@
package org.springframework.scheduling.annotation;
import java.lang.reflect.Method;
+
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
@@ -33,6 +34,8 @@ import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
+import org.springframework.scheduling.config.CronTask;
+import org.springframework.scheduling.config.IntervalTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.ScheduledMethodRunnable;
import org.springframework.util.Assert;
@@ -74,13 +77,7 @@ public class ScheduledAnnotationBeanPostProcessor
private ApplicationContext applicationContext;
- private ScheduledTaskRegistrar registrar;
-
- private final Map cronTasks = new HashMap();
-
- private final Map fixedDelayTasks = new HashMap();
-
- private final Map fixedRateTasks = new HashMap();
+ private final ScheduledTaskRegistrar registrar = new ScheduledTaskRegistrar();
/**
@@ -104,7 +101,6 @@ public class ScheduledAnnotationBeanPostProcessor
return LOWEST_PRECEDENCE;
}
-
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
@@ -124,9 +120,11 @@ public class ScheduledAnnotationBeanPostProcessor
// found a @Scheduled method on the target class for this JDK proxy -> is it
// also present on the proxy itself?
method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
- } catch (SecurityException ex) {
+ }
+ catch (SecurityException ex) {
ReflectionUtils.handleReflectionException(ex);
- } catch (NoSuchMethodException ex) {
+ }
+ catch (NoSuchMethodException ex) {
throw new IllegalStateException(String.format(
"@Scheduled method '%s' found on bean target class '%s', " +
"but not found in any interface(s) for bean JDK proxy. Either " +
@@ -137,26 +135,27 @@ public class ScheduledAnnotationBeanPostProcessor
}
Runnable runnable = new ScheduledMethodRunnable(bean, method);
boolean processedSchedule = false;
- String errorMessage = "Exactly one of 'cron', 'fixedDelay', or 'fixedRate' is required.";
+ String errorMessage = "Exactly one of the 'cron', 'fixedDelay', or 'fixedRate' attributes is required.";
String cron = annotation.cron();
if (!"".equals(cron)) {
processedSchedule = true;
if (embeddedValueResolver != null) {
cron = embeddedValueResolver.resolveStringValue(cron);
}
- cronTasks.put(runnable, cron);
+ registrar.addCronTask(new CronTask(runnable, cron));
}
+ long initialDelay = annotation.initialDelay();
long fixedDelay = annotation.fixedDelay();
if (fixedDelay >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
- fixedDelayTasks.put(runnable, fixedDelay);
+ registrar.addFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay));
}
long fixedRate = annotation.fixedRate();
if (fixedRate >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
- fixedRateTasks.put(runnable, fixedRate);
+ registrar.addFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay));
}
Assert.isTrue(processedSchedule, errorMessage);
}
@@ -170,17 +169,8 @@ public class ScheduledAnnotationBeanPostProcessor
return;
}
- Map configurers = applicationContext.getBeansOfType(SchedulingConfigurer.class);
-
- if (this.cronTasks.isEmpty() && this.fixedDelayTasks.isEmpty() &&
- this.fixedRateTasks.isEmpty() && configurers.isEmpty()) {
- return;
- }
-
- this.registrar = new ScheduledTaskRegistrar();
- this.registrar.setCronTasks(this.cronTasks);
- this.registrar.setFixedDelayTasks(this.fixedDelayTasks);
- this.registrar.setFixedRateTasks(this.fixedRateTasks);
+ Map configurers =
+ this.applicationContext.getBeansOfType(SchedulingConfigurer.class);
if (this.scheduler != null) {
this.registrar.setScheduler(this.scheduler);
@@ -190,19 +180,23 @@ public class ScheduledAnnotationBeanPostProcessor
configurer.configureTasks(this.registrar);
}
- if (registrar.getScheduler() == null) {
+ if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
Map schedulers = new HashMap();
schedulers.putAll(applicationContext.getBeansOfType(TaskScheduler.class));
schedulers.putAll(applicationContext.getBeansOfType(ScheduledExecutorService.class));
if (schedulers.size() == 0) {
// do nothing -> fall back to default scheduler
- } else if (schedulers.size() == 1) {
+ }
+ else if (schedulers.size() == 1) {
this.registrar.setScheduler(schedulers.values().iterator().next());
- } else if (schedulers.size() >= 2){
- throw new IllegalStateException("More than one TaskScheduler and/or ScheduledExecutorService " +
- "exist within the context. Remove all but one of the beans; or implement the " +
- "SchedulingConfigurer interface and call ScheduledTaskRegistrar#setScheduler " +
- "explicitly within the configureTasks() callback. Found the following beans: " + schedulers.keySet());
+ }
+ else if (schedulers.size() >= 2){
+ throw new IllegalStateException(
+ "More than one TaskScheduler and/or ScheduledExecutorService " +
+ "exist within the context. Remove all but one of the beans; or " +
+ "implement the SchedulingConfigurer interface and call " +
+ "ScheduledTaskRegistrar#setScheduler explicitly within the " +
+ "configureTasks() callback. Found the following beans: " + schedulers.keySet());
}
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/SchedulingConfigurer.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/SchedulingConfigurer.java
index d4787c0e4cc..095ff700a7c 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/SchedulingConfigurer.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/SchedulingConfigurer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -19,12 +19,15 @@ package org.springframework.scheduling.annotation;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
/**
- * Interface to be implemented by @{@link org.springframework.context.annotation.Configuration}
- * classes annotated with @{@link EnableScheduling} that wish to register scheduled tasks
- * in a programmatic fashion as opposed to the declarative approach of
- * using the @{@link Scheduled} annotation. For example, this may be necessary when
- * implementing {@link org.springframework.scheduling.Trigger Trigger}-based tasks, which
- * are not supported by the {@code @Scheduled} annotation.
+ * Optional interface to be implemented by @{@link
+ * org.springframework.context.annotation.Configuration Configuration} classes annotated
+ * with @{@link EnableScheduling}. Typically used for setting a specific
+ * {@link org.springframework.scheduling.TaskScheduler TaskScheduler} bean to be used when
+ * executing scheduled tasks or for registering scheduled tasks in a programmatic
+ * fashion as opposed to the declarative approach of using the @{@link Scheduled}
+ * annotation. For example, this may be necessary when implementing {@link
+ * org.springframework.scheduling.Trigger Trigger}-based tasks, which are not supported by
+ * the {@code @Scheduled} annotation.
*
* See @{@link EnableScheduling} for detailed usage examples.
*
@@ -35,6 +38,12 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
*/
public interface SchedulingConfigurer {
+ /**
+ * Callback allowing a {@link org.springframework.scheduling.TaskScheduler
+ * TaskScheduler} and specific {@link org.springframework.scheduling.config.Task Task}
+ * instances to be registered against the given the {@link ScheduledTaskRegistrar}
+ * @param taskRegistrar the registrar to be configured.
+ */
void configureTasks(ScheduledTaskRegistrar taskRegistrar);
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/CronTask.java b/spring-context/src/main/java/org/springframework/scheduling/config/CronTask.java
new file mode 100644
index 00000000000..cf859ab4273
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/scheduling/config/CronTask.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2012 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.scheduling.config;
+
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.scheduling.support.CronTrigger;
+
+/**
+ * {@link TriggerTask} implementation defining a {@code Runnable} to be executed according
+ * to a {@linkplain org.springframework.scheduling.support.CronSequenceGenerator standard
+ * cron expression}.
+ *
+ * @author Chris Beams
+ * @since 3.2
+ * @see Scheduled#cron()
+ * @see ScheduledTaskRegistrar#setCronTasksList(java.util.List)
+ * @see org.springframework.scheduling.TaskScheduler
+ */
+public class CronTask extends TriggerTask {
+
+ private String expression;
+
+
+ /**
+ * Create a new {@code CronTask}.
+ * @param runnable the underlying task to execute
+ * @param expression cron expression defining when the task should be executed
+ */
+ public CronTask(Runnable runnable, String expression) {
+ this(runnable, new CronTrigger(expression));
+ }
+
+ /**
+ * Create a new {@code CronTask}.
+ * @param runnable the underlying task to execute
+ * @param cronTrigger the cron trigger defining when the task should be executed
+ */
+ public CronTask(Runnable runnable, CronTrigger cronTrigger) {
+ super(runnable, cronTrigger);
+ this.expression = cronTrigger.getExpression();
+ }
+
+ public String getExpression() {
+ return expression;
+ }
+}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/IntervalTask.java b/spring-context/src/main/java/org/springframework/scheduling/config/IntervalTask.java
new file mode 100644
index 00000000000..4b39927e8f7
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/scheduling/config/IntervalTask.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2002-2012 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.scheduling.config;
+
+/**
+ * {@link Task} implementation defining a {@code Runnable} to be executed at a given
+ * millisecond interval which may be treated as fixed-rate or fixed-delay depending on
+ * context.
+ *
+ * @author Chris Beams
+ * @since 3.2
+ * @see org.springframework.scheduling.annotation.Scheduled#fixedRate()
+ * @see org.springframework.scheduling.annotation.Scheduled#fixedDelay()
+ * @see ScheduledTaskRegistrar#setFixedRateTasksList(java.util.List)
+ * @see ScheduledTaskRegistrar#setFixedDelayTasksList(java.util.List)
+ * @see org.springframework.scheduling.TaskScheduler
+ */
+public class IntervalTask extends Task {
+
+ private final long interval;
+
+ private final long initialDelay;
+
+
+ /**
+ * Create a new {@code IntervalTask}.
+ * @param runnable the underlying task to execute
+ * @param interval how often in milliseconds the task should be executed
+ * @param initialDelay initial delay before first execution of the task
+ */
+ public IntervalTask(Runnable runnable, long interval, long initialDelay) {
+ super(runnable);
+ this.initialDelay = initialDelay;
+ this.interval = interval;
+ }
+
+ /**
+ * Create a new {@code IntervalTask} with no initial delay.
+ * @param runnable the underlying task to execute
+ * @param interval how often in milliseconds the task should be executed
+ */
+ public IntervalTask(Runnable runnable, long interval) {
+ this(runnable, interval, 0);
+ }
+
+
+ public long getInterval() {
+ return interval;
+ }
+
+ public long getInitialDelay() {
+ return initialDelay;
+ }
+}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java
index 1898441e217..b5b0b44ddec 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -16,8 +16,10 @@
package org.springframework.scheduling.config;
-import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.Date;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
@@ -33,8 +35,8 @@ import org.springframework.scheduling.support.CronTrigger;
import org.springframework.util.Assert;
/**
- * Helper bean for registering tasks with a {@link TaskScheduler},
- * typically using cron expressions.
+ * Helper bean for registering tasks with a {@link TaskScheduler}, typically using cron
+ * expressions.
*
*
As of Spring 3.1, {@code ScheduledTaskRegistrar} has a more prominent user-facing
* role when used in conjunction with the @{@link
@@ -43,6 +45,7 @@ import org.springframework.util.Assert;
* SchedulingConfigurer} callback interface.
*
* @author Juergen Hoeller
+ * @author Chris Beams
* @since 3.0
* @see org.springframework.scheduling.annotation.EnableAsync
* @see org.springframework.scheduling.annotation.SchedulingConfigurer
@@ -53,19 +56,19 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean
private ScheduledExecutorService localExecutor;
- private Map triggerTasks;
+ private List triggerTasks;
- private Map cronTasks;
+ private List cronTasks;
- private Map fixedRateTasks;
+ private List fixedRateTasks;
- private Map fixedDelayTasks;
+ private List fixedDelayTasks;
private final Set> scheduledFutures = new LinkedHashSet>();
/**
- * Set the TaskScheduler to register scheduled tasks with.
+ * Set the {@link TaskScheduler} to register scheduled tasks with.
*/
public void setTaskScheduler(TaskScheduler taskScheduler) {
Assert.notNull(taskScheduler, "TaskScheduler must not be null");
@@ -73,9 +76,9 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean
}
/**
- * Set the {@link org.springframework.scheduling.TaskScheduler} to register scheduled
- * tasks with, or a {@link java.util.concurrent.ScheduledExecutorService} to be
- * wrapped as a TaskScheduler.
+ * Set the {@link TaskScheduler} to register scheduled tasks with, or a
+ * {@link java.util.concurrent.ScheduledExecutorService} to be wrapped as a
+ * {@code TaskScheduler}.
*/
public void setScheduler(Object scheduler) {
Assert.notNull(scheduler, "Scheduler object must not be null");
@@ -91,17 +94,31 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean
}
/**
- * Return the scheduler instance for this registrar (may be null)
+ * Return the {@link TaskScheduler} instance for this registrar (may be {@code null}).
*/
public TaskScheduler getScheduler() {
return this.taskScheduler;
}
+
/**
* Specify triggered tasks as a Map of Runnables (the tasks) and Trigger objects
* (typically custom implementations of the {@link Trigger} interface).
*/
public void setTriggerTasks(Map triggerTasks) {
+ this.triggerTasks = new ArrayList();
+ for (Map.Entry task : triggerTasks.entrySet()) {
+ this.triggerTasks.add(new TriggerTask(task.getKey(), task.getValue()));
+ }
+ }
+
+ /**
+ * Specify triggered tasks as a list of {@link TriggerTask} objects. Primarily used
+ * by {@code } namespace parsing.
+ * @since 3.2
+ * @see ScheduledTasksBeanDefinitionParser
+ */
+ public void setTriggerTasksList(List triggerTasks) {
this.triggerTasks = triggerTasks;
}
@@ -110,6 +127,19 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean
* @see CronTrigger
*/
public void setCronTasks(Map cronTasks) {
+ this.cronTasks = new ArrayList();
+ for (Map.Entry task : cronTasks.entrySet()) {
+ this.addCronTask(task.getKey(), task.getValue());
+ }
+ }
+
+ /**
+ * Specify triggered tasks as a list of {@link CronTask} objects. Primarily used by
+ * {@code } namespace parsing.
+ * @since 3.2
+ * @see ScheduledTasksBeanDefinitionParser
+ */
+ public void setCronTasksList(List cronTasks) {
this.cronTasks = cronTasks;
}
@@ -118,39 +148,79 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean
* @see TaskScheduler#scheduleAtFixedRate(Runnable, long)
*/
public void setFixedRateTasks(Map fixedRateTasks) {
+ this.fixedRateTasks = new ArrayList();
+ for (Map.Entry task : fixedRateTasks.entrySet()) {
+ this.addFixedRateTask(task.getKey(), task.getValue());
+ }
+ }
+
+ /**
+ * Specify fixed-rate tasks as a list of {@link IntervalTask} objects. Primarily used
+ * by {@code } namespace parsing.
+ * @since 3.2
+ * @see ScheduledTasksBeanDefinitionParser
+ */
+ public void setFixedRateTasksList(List fixedRateTasks) {
this.fixedRateTasks = fixedRateTasks;
}
+ /**
+ * Specify triggered tasks as a Map of Runnables (the tasks) and fixed-delay values.
+ * @see TaskScheduler#scheduleWithFixedDelay(Runnable, long)
+ */
+ public void setFixedDelayTasks(Map fixedDelayTasks) {
+ this.fixedDelayTasks = new ArrayList();
+ for (Map.Entry task : fixedDelayTasks.entrySet()) {
+ this.addFixedDelayTask(task.getKey(), task.getValue());
+ }
+ }
+
+ /**
+ * Specify fixed-delay tasks as a list of {@link IntervalTask} objects. Primarily used
+ * by {@code } namespace parsing.
+ * @since 3.2
+ * @see ScheduledTasksBeanDefinitionParser
+ */
+ public void setFixedDelayTasksList(List fixedDelayTasks) {
+ this.fixedDelayTasks = fixedDelayTasks;
+ }
+
/**
* Add a Runnable task to be triggered per the given {@link Trigger}.
* @see TaskScheduler#scheduleAtFixedRate(Runnable, long)
*/
public void addTriggerTask(Runnable task, Trigger trigger) {
+ this.addTriggerTask(new TriggerTask(task, trigger));
+ }
+
+ /**
+ * Add a {@code TriggerTask}.
+ * @since 3.2
+ * @see TaskScheduler#scheduleAtFixedRate(Runnable, long)
+ */
+ public void addTriggerTask(TriggerTask task) {
if (this.triggerTasks == null) {
- this.triggerTasks = new HashMap();
+ this.triggerTasks = new ArrayList();
}
- this.triggerTasks.put(task, trigger);
+ this.triggerTasks.add(task);
}
/**
* Add a Runnable task to be triggered per the given cron expression
*/
- public void addCronTask(Runnable task, String cronExpression) {
- if (this.cronTasks == null) {
- this.cronTasks = new HashMap();
- }
- this.cronTasks.put(task, cronExpression);
+ public void addCronTask(Runnable task, String expression) {
+ this.addCronTask(new CronTask(task, expression));
}
/**
- * Add a Runnable task to be triggered with the given fixed delay.
- * @see TaskScheduler#scheduleWithFixedDelay(Runnable, long)
+ * Add a {@link CronTask}.
+ * @since 3.2
*/
- public void addFixedDelayTask(Runnable task, long delay) {
- if (this.fixedDelayTasks == null) {
- this.fixedDelayTasks = new HashMap();
+ public void addCronTask(CronTask task) {
+ if (this.cronTasks == null) {
+ this.cronTasks = new ArrayList();
}
- this.fixedDelayTasks.put(task, delay);
+ this.cronTasks.add(task);
}
/**
@@ -158,49 +228,103 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean
* @see TaskScheduler#scheduleAtFixedRate(Runnable, long)
*/
public void addFixedRateTask(Runnable task, long period) {
+ this.addFixedRateTask(new IntervalTask(task, period, 0));
+ }
+
+ /**
+ * Add a fixed-rate {@link IntervalTask}.
+ * @since 3.2
+ * @see TaskScheduler#scheduleAtFixedRate(Runnable, long)
+ */
+ public void addFixedRateTask(IntervalTask task) {
if (this.fixedRateTasks == null) {
- this.fixedRateTasks = new HashMap();
+ this.fixedRateTasks = new ArrayList();
}
- this.fixedRateTasks.put(task, period);
+ this.fixedRateTasks.add(task);
}
/**
- * Specify triggered tasks as a Map of Runnables (the tasks) and fixed-delay values.
+ * Add a Runnable task to be triggered with the given fixed delay.
* @see TaskScheduler#scheduleWithFixedDelay(Runnable, long)
*/
- public void setFixedDelayTasks(Map fixedDelayTasks) {
- this.fixedDelayTasks = fixedDelayTasks;
+ public void addFixedDelayTask(Runnable task, long delay) {
+ this.addFixedDelayTask(new IntervalTask(task, delay, 0));
+ }
+
+ /**
+ * Add a fixed-delay {@link IntervalTask}.
+ * @since 3.2
+ * @see TaskScheduler#scheduleWithFixedDelay(Runnable, long)
+ */
+ public void addFixedDelayTask(IntervalTask task) {
+ if (this.fixedDelayTasks == null) {
+ this.fixedDelayTasks = new ArrayList();
+ }
+ this.fixedDelayTasks.add(task);
}
+ /**
+ * Return whether this {@code ScheduledTaskRegistrar} has any tasks registered.
+ * @since 3.2
+ */
+ public boolean hasTasks() {
+ return (this.fixedRateTasks != null && !this.fixedRateTasks.isEmpty()) ||
+ (this.fixedDelayTasks != null && !this.fixedDelayTasks.isEmpty()) ||
+ (this.cronTasks != null && !this.cronTasks.isEmpty()) ||
+ (this.triggerTasks != null && !this.triggerTasks.isEmpty());
+ }
+ /**
+ * Schedule all registered tasks against the underlying {@linkplain
+ * #setTaskScheduler(TaskScheduler) task scheduler.
+ */
public void afterPropertiesSet() {
+ long now = System.currentTimeMillis();
+
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
if (this.triggerTasks != null) {
- for (Map.Entry entry : this.triggerTasks.entrySet()) {
- this.scheduledFutures.add(this.taskScheduler.schedule(entry.getKey(), entry.getValue()));
+ for (TriggerTask task : triggerTasks) {
+ this.scheduledFutures.add(this.taskScheduler.schedule(
+ task.getRunnable(), task.getTrigger()));
}
}
if (this.cronTasks != null) {
- for (Map.Entry entry : this.cronTasks.entrySet()) {
- this.scheduledFutures.add(this.taskScheduler.schedule(entry.getKey(), new CronTrigger(entry.getValue())));
+ for (CronTask task : cronTasks) {
+ this.scheduledFutures.add(this.taskScheduler.schedule(
+ task.getRunnable(), task.getTrigger()));
}
}
if (this.fixedRateTasks != null) {
- for (Map.Entry entry : this.fixedRateTasks.entrySet()) {
- this.scheduledFutures.add(this.taskScheduler.scheduleAtFixedRate(entry.getKey(), entry.getValue()));
+ for (IntervalTask task : fixedRateTasks) {
+ if (task.getInitialDelay() > 0) {
+ Date startTime = new Date(now + task.getInitialDelay());
+ this.scheduledFutures.add(this.taskScheduler.scheduleAtFixedRate(
+ task.getRunnable(), startTime, task.getInterval()));
+ }
+ else {
+ this.scheduledFutures.add(this.taskScheduler.scheduleAtFixedRate(
+ task.getRunnable(), task.getInterval()));
+ }
}
}
if (this.fixedDelayTasks != null) {
- for (Map.Entry entry : this.fixedDelayTasks.entrySet()) {
- this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay(entry.getKey(), entry.getValue()));
+ for (IntervalTask task : fixedDelayTasks) {
+ if (task.getInitialDelay() > 0) {
+ Date startTime = new Date(now + task.getInitialDelay());
+ this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay(
+ task.getRunnable(), startTime, task.getInterval()));
+ }
+ else {
+ this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay(
+ task.getRunnable(), task.getInterval()));
+ }
}
}
}
-
public void destroy() {
for (ScheduledFuture> future : this.scheduledFutures) {
future.cancel(true);
diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParser.java b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParser.java
index 6e29ad4f102..828528afab4 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParser.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -16,27 +16,29 @@
package org.springframework.scheduling.config;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
-import org.springframework.beans.factory.support.ManagedMap;
+import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
/**
* Parser for the 'scheduled-tasks' element of the scheduling namespace.
*
* @author Mark Fisher
+ * @author Chris Beams
* @since 3.0
*/
public class ScheduledTasksBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
private static final String ELEMENT_SCHEDULED = "scheduled";
+ private static final long ZERO_INITIAL_DELAY = 0;
@Override
protected boolean shouldGenerateId() {
@@ -51,10 +53,10 @@ public class ScheduledTasksBeanDefinitionParser extends AbstractSingleBeanDefini
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
builder.setLazyInit(false); // lazy scheduled tasks are a contradiction in terms -> force to false
- ManagedMap cronTaskMap = new ManagedMap();
- ManagedMap fixedDelayTaskMap = new ManagedMap();
- ManagedMap fixedRateTaskMap = new ManagedMap();
- ManagedMap triggerTaskMap = new ManagedMap();
+ ManagedList cronTaskList = new ManagedList();
+ ManagedList fixedDelayTaskList = new ManagedList();
+ ManagedList fixedRateTaskList = new ManagedList();
+ ManagedList triggerTaskList = new ManagedList();
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
@@ -64,55 +66,67 @@ public class ScheduledTasksBeanDefinitionParser extends AbstractSingleBeanDefini
Element taskElement = (Element) child;
String ref = taskElement.getAttribute("ref");
String method = taskElement.getAttribute("method");
-
+
// Check that 'ref' and 'method' are specified
if (!StringUtils.hasText(ref) || !StringUtils.hasText(method)) {
parserContext.getReaderContext().error("Both 'ref' and 'method' are required", taskElement);
// Continue with the possible next task element
continue;
}
-
- RuntimeBeanReference runnableBeanRef = new RuntimeBeanReference(
- createRunnableBean(ref, method, taskElement, parserContext));
String cronAttribute = taskElement.getAttribute("cron");
String fixedDelayAttribute = taskElement.getAttribute("fixed-delay");
String fixedRateAttribute = taskElement.getAttribute("fixed-rate");
String triggerAttribute = taskElement.getAttribute("trigger");
+ String initialDelayAttribute = taskElement.getAttribute("initial-delay");
boolean hasCronAttribute = StringUtils.hasText(cronAttribute);
boolean hasFixedDelayAttribute = StringUtils.hasText(fixedDelayAttribute);
boolean hasFixedRateAttribute = StringUtils.hasText(fixedRateAttribute);
boolean hasTriggerAttribute = StringUtils.hasText(triggerAttribute);
+ boolean hasInitialDelayAttribute = StringUtils.hasText(initialDelayAttribute);
- if (!(hasCronAttribute | hasFixedDelayAttribute | hasFixedRateAttribute | hasTriggerAttribute)) {
+ if (!(hasCronAttribute || hasFixedDelayAttribute || hasFixedRateAttribute || hasTriggerAttribute)) {
parserContext.getReaderContext().error(
- "exactly one of the 'cron', 'fixed-delay', 'fixed-rate', or 'trigger' attributes is required", taskElement);
- // Continue with the possible next task element
- continue;
+ "one of the 'cron', 'fixed-delay', 'fixed-rate', or 'trigger' attributes is required", taskElement);
+ continue; // with the possible next task element
}
- if (hasCronAttribute) {
- cronTaskMap.put(runnableBeanRef, cronAttribute);
+ if (hasInitialDelayAttribute && (hasCronAttribute || hasTriggerAttribute)) {
+ parserContext.getReaderContext().error(
+ "the 'initial-delay' attribute may not be used with cron and trigger tasks", taskElement);
+ continue; // with the possible next task element
}
+
+ String runnableName =
+ runnableReference(ref, method, taskElement, parserContext).getBeanName();
+
if (hasFixedDelayAttribute) {
- fixedDelayTaskMap.put(runnableBeanRef, fixedDelayAttribute);
+ fixedDelayTaskList.add(intervalTaskReference(runnableName,
+ initialDelayAttribute, fixedDelayAttribute, taskElement, parserContext));
}
if (hasFixedRateAttribute) {
- fixedRateTaskMap.put(runnableBeanRef, fixedRateAttribute);
+ fixedRateTaskList.add(intervalTaskReference(runnableName,
+ initialDelayAttribute, fixedRateAttribute, taskElement, parserContext));
+ }
+ if (hasCronAttribute) {
+ cronTaskList.add(cronTaskReference(runnableName, cronAttribute,
+ taskElement, parserContext));
}
if (hasTriggerAttribute) {
- triggerTaskMap.put(runnableBeanRef, new RuntimeBeanReference(triggerAttribute));
+ String triggerName = new RuntimeBeanReference(triggerAttribute).getBeanName();
+ triggerTaskList.add(triggerTaskReference(runnableName, triggerName,
+ taskElement, parserContext));
}
}
String schedulerRef = element.getAttribute("scheduler");
if (StringUtils.hasText(schedulerRef)) {
builder.addPropertyReference("taskScheduler", schedulerRef);
}
- builder.addPropertyValue("cronTasks", cronTaskMap);
- builder.addPropertyValue("fixedDelayTasks", fixedDelayTaskMap);
- builder.addPropertyValue("fixedRateTasks", fixedRateTaskMap);
- builder.addPropertyValue("triggerTasks", triggerTaskMap);
+ builder.addPropertyValue("cronTasksList", cronTaskList);
+ builder.addPropertyValue("fixedDelayTasksList", fixedDelayTaskList);
+ builder.addPropertyValue("fixedRateTasksList", fixedRateTaskList);
+ builder.addPropertyValue("triggerTasksList", triggerTaskList);
}
private boolean isScheduledElement(Node node, ParserContext parserContext) {
@@ -120,16 +134,49 @@ public class ScheduledTasksBeanDefinitionParser extends AbstractSingleBeanDefini
ELEMENT_SCHEDULED.equals(parserContext.getDelegate().getLocalName(node));
}
- private String createRunnableBean(String ref, String method, Element taskElement, ParserContext parserContext) {
+ private RuntimeBeanReference runnableReference(String ref, String method, Element taskElement, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.scheduling.support.ScheduledMethodRunnable");
builder.addConstructorArgReference(ref);
builder.addConstructorArgValue(method);
+ return beanReference(taskElement, parserContext, builder);
+ }
+
+ private RuntimeBeanReference intervalTaskReference(String runnableBeanName,
+ String initialDelay, String interval, Element taskElement, ParserContext parserContext) {
+ BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
+ "org.springframework.scheduling.config.IntervalTask");
+ builder.addConstructorArgReference(runnableBeanName);
+ builder.addConstructorArgValue(interval);
+ builder.addConstructorArgValue(StringUtils.hasLength(initialDelay) ? initialDelay : ZERO_INITIAL_DELAY);
+ return beanReference(taskElement, parserContext, builder);
+ }
+
+ private RuntimeBeanReference cronTaskReference(String runnableBeanName,
+ String cronExpression, Element taskElement, ParserContext parserContext) {
+ BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
+ "org.springframework.scheduling.config.CronTask");
+ builder.addConstructorArgReference(runnableBeanName);
+ builder.addConstructorArgValue(cronExpression);
+ return beanReference(taskElement, parserContext, builder);
+ }
+
+ private RuntimeBeanReference triggerTaskReference(String runnableBeanName,
+ String triggerBeanName, Element taskElement, ParserContext parserContext) {
+ BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
+ "org.springframework.scheduling.config.TriggerTask");
+ builder.addConstructorArgReference(runnableBeanName);
+ builder.addConstructorArgReference(triggerBeanName);
+ return beanReference(taskElement, parserContext, builder);
+ }
+
+ private RuntimeBeanReference beanReference(Element taskElement,
+ ParserContext parserContext, BeanDefinitionBuilder builder) {
// Extract the source of the current task
builder.getRawBeanDefinition().setSource(parserContext.extractSource(taskElement));
String generatedName = parserContext.getReaderContext().generateBeanName(builder.getRawBeanDefinition());
parserContext.registerBeanComponent(new BeanComponentDefinition(builder.getBeanDefinition(), generatedName));
- return generatedName;
+ return new RuntimeBeanReference(generatedName);
}
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/Task.java b/spring-context/src/main/java/org/springframework/scheduling/config/Task.java
new file mode 100644
index 00000000000..02dac4cba46
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/scheduling/config/Task.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2012 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.scheduling.config;
+
+/**
+ * Holder class defining a {@code Runnable} to be executed as a task, typically at a
+ * scheduled time or interval. See subclass hierarchy for various scheduling approaches.
+ *
+ * @author Chris Beams
+ * @since 3.2
+ */
+public class Task {
+
+ private final Runnable runnable;
+
+
+ /**
+ * Create a new {@code Task}.
+ * @param runnable the underlying task to execute.
+ */
+ public Task(Runnable runnable) {
+ this.runnable = runnable;
+ }
+
+
+ public Runnable getRunnable() {
+ return runnable;
+ }
+}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/TriggerTask.java b/spring-context/src/main/java/org/springframework/scheduling/config/TriggerTask.java
new file mode 100644
index 00000000000..6d0aa6c8136
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/scheduling/config/TriggerTask.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2012 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.scheduling.config;
+
+import org.springframework.scheduling.Trigger;
+
+/**
+ * {@link Task} implementation defining a {@code Runnable} to be executed according to a
+ * given {@link Trigger}.
+ *
+ * @author Chris Beams
+ * @since 3.2
+ * @see Trigger#nextExecutionTime(org.springframework.scheduling.TriggerContext)
+ * @see ScheduledTaskRegistrar#setTriggerTasksList(java.util.List)
+ * @see org.springframework.scheduling.TaskScheduler#schedule(Runnable, Trigger)
+ */
+public class TriggerTask extends Task {
+
+ private final Trigger trigger;
+
+
+ /**
+ * Create a new {@link TriggerTask}.
+ * @param runnable the underlying task to execute
+ * @param trigger specifies when the task should be executed
+ */
+ public TriggerTask(Runnable runnable, Trigger trigger) {
+ super(runnable);
+ this.trigger = trigger;
+ }
+
+
+ public Trigger getTrigger() {
+ return trigger;
+ }
+}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java b/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java
index 2e1526ac66c..b1753f92757 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java
@@ -339,6 +339,10 @@ public class CronSequenceGenerator {
return result;
}
+ String getExpression() {
+ return this.expression;
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof CronSequenceGenerator)) {
diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java b/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java
index a3c8fc8df03..f23b327cf80 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java
@@ -72,6 +72,9 @@ public class CronTrigger implements Trigger {
return this.sequenceGenerator.next(date);
}
+ public String getExpression() {
+ return this.sequenceGenerator.getExpression();
+ }
@Override
public boolean equals(Object obj) {
diff --git a/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-3.2.xsd b/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-3.2.xsd
index 3091b0ba071..cb05f8fcd3c 100644
--- a/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-3.2.xsd
+++ b/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-3.2.xsd
@@ -229,6 +229,14 @@
]]>
+
+
+
+
+
fixedDelayTasks = (Map)
+ @SuppressWarnings("unchecked")
+ List fixedDelayTasks = (List)
new DirectFieldAccessor(registrar).getPropertyValue("fixedDelayTasks");
assertEquals(1, fixedDelayTasks.size());
- ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) fixedDelayTasks.keySet().iterator().next();
+ IntervalTask task = fixedDelayTasks.get(0);
+ ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertEquals(target, targetObject);
assertEquals("fixedDelay", targetMethod.getName());
- assertEquals(new Long(5000), fixedDelayTasks.values().iterator().next());
+ assertEquals(0L, task.getInitialDelay());
+ assertEquals(5000L, task.getInterval());
}
@Test
@@ -80,15 +87,45 @@ public class ScheduledAnnotationBeanPostProcessorTests {
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
- Map fixedRateTasks = (Map)
+ @SuppressWarnings("unchecked")
+ List fixedRateTasks = (List)
new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks");
assertEquals(1, fixedRateTasks.size());
- ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) fixedRateTasks.keySet().iterator().next();
+ IntervalTask task = fixedRateTasks.get(0);
+ ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertEquals(target, targetObject);
assertEquals("fixedRate", targetMethod.getName());
- assertEquals(new Long(3000), fixedRateTasks.values().iterator().next());
+ assertEquals(0L, task.getInitialDelay());
+ assertEquals(3000L, task.getInterval());
+ }
+
+ @Test
+ public void fixedRateTaskWithInitialDelay() {
+ StaticApplicationContext context = new StaticApplicationContext();
+ BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
+ BeanDefinition targetDefinition = new RootBeanDefinition(
+ ScheduledAnnotationBeanPostProcessorTests.FixedRateWithInitialDelayTestBean.class);
+ context.registerBeanDefinition("postProcessor", processorDefinition);
+ context.registerBeanDefinition("target", targetDefinition);
+ context.refresh();
+ Object postProcessor = context.getBean("postProcessor");
+ Object target = context.getBean("target");
+ ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
+ new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
+ @SuppressWarnings("unchecked")
+ List fixedRateTasks = (List)
+ new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks");
+ assertEquals(1, fixedRateTasks.size());
+ IntervalTask task = fixedRateTasks.get(0);
+ ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
+ Object targetObject = runnable.getTarget();
+ Method targetMethod = runnable.getMethod();
+ assertEquals(target, targetObject);
+ assertEquals("fixedRate", targetMethod.getName());
+ assertEquals(1000L, task.getInitialDelay());
+ assertEquals(3000L, task.getInterval());
}
@Test
@@ -104,15 +141,17 @@ public class ScheduledAnnotationBeanPostProcessorTests {
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
- Map cronTasks = (Map)
+ @SuppressWarnings("unchecked")
+ List cronTasks = (List)
new DirectFieldAccessor(registrar).getPropertyValue("cronTasks");
assertEquals(1, cronTasks.size());
- ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) cronTasks.keySet().iterator().next();
+ CronTask task = cronTasks.get(0);
+ ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertEquals(target, targetObject);
assertEquals("cron", targetMethod.getName());
- assertEquals("*/7 * * * * ?", cronTasks.values().iterator().next());
+ assertEquals("*/7 * * * * ?", task.getExpression());
Thread.sleep(10000);
}
@@ -129,15 +168,17 @@ public class ScheduledAnnotationBeanPostProcessorTests {
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
- Map fixedRateTasks = (Map)
+ @SuppressWarnings("unchecked")
+ List fixedRateTasks = (List)
new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks");
assertEquals(1, fixedRateTasks.size());
- ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) fixedRateTasks.keySet().iterator().next();
+ IntervalTask task = fixedRateTasks.get(0);
+ ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertEquals(target, targetObject);
assertEquals("checkForUpdates", targetMethod.getName());
- assertEquals(new Long(5000), fixedRateTasks.values().iterator().next());
+ assertEquals(5000L, task.getInterval());
}
@Test
@@ -153,15 +194,17 @@ public class ScheduledAnnotationBeanPostProcessorTests {
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
- Map cronTasks = (Map)
+ @SuppressWarnings("unchecked")
+ List cronTasks = (List)
new DirectFieldAccessor(registrar).getPropertyValue("cronTasks");
assertEquals(1, cronTasks.size());
- ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) cronTasks.keySet().iterator().next();
+ CronTask task = cronTasks.get(0);
+ ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertEquals(target, targetObject);
assertEquals("generateReport", targetMethod.getName());
- assertEquals("0 0 * * * ?", cronTasks.values().iterator().next());
+ assertEquals("0 0 * * * ?", task.getExpression());
}
@Test
@@ -183,15 +226,17 @@ public class ScheduledAnnotationBeanPostProcessorTests {
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
- Map cronTasks = (Map)
+ @SuppressWarnings("unchecked")
+ List cronTasks = (List)
new DirectFieldAccessor(registrar).getPropertyValue("cronTasks");
assertEquals(1, cronTasks.size());
- ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) cronTasks.keySet().iterator().next();
+ CronTask task = cronTasks.get(0);
+ ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertEquals(target, targetObject);
assertEquals("x", targetMethod.getName());
- assertEquals(businessHoursCronExpression, cronTasks.values().iterator().next());
+ assertEquals(businessHoursCronExpression, task.getExpression());
}
@Test
@@ -213,15 +258,17 @@ public class ScheduledAnnotationBeanPostProcessorTests {
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
- Map cronTasks = (Map)
+ @SuppressWarnings("unchecked")
+ List cronTasks = (List)
new DirectFieldAccessor(registrar).getPropertyValue("cronTasks");
assertEquals(1, cronTasks.size());
- ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) cronTasks.keySet().iterator().next();
+ CronTask task = cronTasks.get(0);
+ ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertEquals(target, targetObject);
assertEquals("y", targetMethod.getName());
- assertEquals(businessHoursCronExpression, cronTasks.values().iterator().next());
+ assertEquals(businessHoursCronExpression, task.getExpression());
}
@Test(expected = BeanCreationException.class)
@@ -236,14 +283,19 @@ public class ScheduledAnnotationBeanPostProcessorTests {
}
@Test(expected = IllegalArgumentException.class)
- public void invalidCron() {
+ public void invalidCron() throws Throwable {
StaticApplicationContext context = new StaticApplicationContext();
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(
ScheduledAnnotationBeanPostProcessorTests.InvalidCronTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
- context.refresh();
+ try {
+ context.refresh();
+ fail("expected exception");
+ } catch (BeanCreationException ex) {
+ throw ex.getRootCause();
+ }
}
@Test(expected = BeanCreationException.class)
@@ -269,7 +321,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
}
- public static class FixedDelayTestBean {
+ static class FixedDelayTestBean {
@Scheduled(fixedDelay=5000)
public void fixedDelay() {
@@ -277,7 +329,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
}
- public static class FixedRateTestBean {
+ static class FixedRateTestBean {
@Scheduled(fixedRate=3000)
public void fixedRate() {
@@ -285,7 +337,15 @@ public class ScheduledAnnotationBeanPostProcessorTests {
}
- public static class CronTestBean {
+ static class FixedRateWithInitialDelayTestBean {
+
+ @Scheduled(initialDelay=1000, fixedRate=3000)
+ public void fixedRate() {
+ }
+ }
+
+
+ static class CronTestBean {
@Scheduled(cron="*/7 * * * * ?")
public void cron() throws IOException {
@@ -295,7 +355,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
}
- private static class EmptyAnnotationTestBean {
+ static class EmptyAnnotationTestBean {
@Scheduled
public void invalid() {
@@ -304,7 +364,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
}
- private static class InvalidCronTestBean {
+ static class InvalidCronTestBean {
@Scheduled(cron="abc")
public void invalid() {
@@ -313,7 +373,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
}
- private static class NonVoidReturnTypeTestBean {
+ static class NonVoidReturnTypeTestBean {
@Scheduled(fixedRate=3000)
public String invalid() {
@@ -323,7 +383,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
}
- private static class NonEmptyParamListTestBean {
+ static class NonEmptyParamListTestBean {
@Scheduled(fixedRate=3000)
public void invalid(String oops) {
@@ -344,7 +404,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
private static @interface Hourly {}
- private static class MetaAnnotationFixedRateTestBean {
+ static class MetaAnnotationFixedRateTestBean {
@EveryFiveSeconds
public void checkForUpdates() {
@@ -352,7 +412,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
}
- private static class MetaAnnotationCronTestBean {
+ static class MetaAnnotationCronTestBean {
@Hourly
public void generateReport() {
@@ -360,7 +420,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
}
- private static class PropertyPlaceholderTestBean {
+ static class PropertyPlaceholderTestBean {
@Scheduled(cron = "${schedules.businessHours}")
public void x() {
@@ -370,11 +430,11 @@ public class ScheduledAnnotationBeanPostProcessorTests {
@Scheduled(cron = "${schedules.businessHours}")
@Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
+ @Retention(RetentionPolicy.RUNTIME)
private static @interface BusinessHours {}
- private static class PropertyPlaceholderMetaAnnotationTestBean {
+ static class PropertyPlaceholderMetaAnnotationTestBean {
@BusinessHours
public void y() {
diff --git a/spring-context/src/test/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParserTests.java b/spring-context/src/test/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParserTests.java
index f24355093f9..5e8f75a3587 100644
--- a/spring-context/src/test/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParserTests.java
+++ b/spring-context/src/test/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParserTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -17,11 +17,9 @@
package org.springframework.scheduling.config;
import java.lang.reflect.Method;
-import java.util.Collection;
import java.util.Date;
-import java.util.Map;
+import java.util.List;
-import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
@@ -32,8 +30,13 @@ import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.support.ScheduledMethodRunnable;
+import static org.hamcrest.CoreMatchers.*;
+
+import static org.junit.Assert.*;
+
/**
* @author Mark Fisher
+ * @author Chris Beams
*/
@SuppressWarnings("unchecked")
public class ScheduledTasksBeanDefinitionParserTests {
@@ -63,9 +66,9 @@ public class ScheduledTasksBeanDefinitionParserTests {
@Test
public void checkTarget() {
- Map tasks = (Map) new DirectFieldAccessor(
+ List tasks = (List) new DirectFieldAccessor(
this.registrar).getPropertyValue("fixedRateTasks");
- Runnable runnable = tasks.keySet().iterator().next();
+ Runnable runnable = tasks.get(0).getRunnable();
assertEquals(ScheduledMethodRunnable.class, runnable.getClass());
Object targetObject = ((ScheduledMethodRunnable) runnable).getTarget();
Method targetMethod = ((ScheduledMethodRunnable) runnable).getMethod();
@@ -75,39 +78,39 @@ public class ScheduledTasksBeanDefinitionParserTests {
@Test
public void fixedRateTasks() {
- Map tasks = (Map) new DirectFieldAccessor(
+ List tasks = (List) new DirectFieldAccessor(
this.registrar).getPropertyValue("fixedRateTasks");
- assertEquals(2, tasks.size());
- Collection values = tasks.values();
- assertTrue(values.contains(new Long(1000)));
- assertTrue(values.contains(new Long(2000)));
+ assertEquals(3, tasks.size());
+ assertEquals(1000L, tasks.get(0).getInterval());
+ assertEquals(2000L, tasks.get(1).getInterval());
+ assertEquals(4000L, tasks.get(2).getInterval());
+ assertEquals(500, tasks.get(2).getInitialDelay());
}
@Test
public void fixedDelayTasks() {
- Map tasks = (Map) new DirectFieldAccessor(
+ List tasks = (List) new DirectFieldAccessor(
this.registrar).getPropertyValue("fixedDelayTasks");
- assertEquals(1, tasks.size());
- Long value = tasks.values().iterator().next();
- assertEquals(new Long(3000), value);
+ assertEquals(2, tasks.size());
+ assertEquals(3000L, tasks.get(0).getInterval());
+ assertEquals(3500L, tasks.get(1).getInterval());
+ assertEquals(250, tasks.get(1).getInitialDelay());
}
@Test
public void cronTasks() {
- Map tasks = (Map) new DirectFieldAccessor(
+ List tasks = (List) new DirectFieldAccessor(
this.registrar).getPropertyValue("cronTasks");
assertEquals(1, tasks.size());
- String expression = tasks.values().iterator().next();
- assertEquals("*/4 * 9-17 * * MON-FRI", expression);
+ assertEquals("*/4 * 9-17 * * MON-FRI", tasks.get(0).getExpression());
}
@Test
public void triggerTasks() {
- Map tasks = (Map) new DirectFieldAccessor(
+ List tasks = (List) new DirectFieldAccessor(
this.registrar).getPropertyValue("triggerTasks");
assertEquals(1, tasks.size());
- Trigger trigger = tasks.values().iterator().next();
- assertEquals(TestTrigger.class, trigger.getClass());
+ assertThat(tasks.get(0).getTrigger(), instanceOf(TestTrigger.class));
}
diff --git a/spring-context/src/test/resources/org/springframework/scheduling/config/scheduledTasksContext.xml b/spring-context/src/test/resources/org/springframework/scheduling/config/scheduledTasksContext.xml
index 30e46c7bb8a..48f5ebdb072 100644
--- a/spring-context/src/test/resources/org/springframework/scheduling/config/scheduledTasksContext.xml
+++ b/spring-context/src/test/resources/org/springframework/scheduling/config/scheduledTasksContext.xml
@@ -11,8 +11,10 @@
+
+
diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt
index 92ce99c761d..64b4ee18a98 100644
--- a/src/dist/changelog.txt
+++ b/src/dist/changelog.txt
@@ -31,6 +31,7 @@ Changes in version 3.2 M1
* add required flag to @RequestBody annotation
* support executor qualification with @Async#value (SPR-6847)
* add convenient WebAppInitializer base classes (SPR-9300)
+* support initial delay attribute for scheduled tasks (SPR-7022)
Changes in version 3.1.1 (2012-02-16)
-------------------------------------
diff --git a/src/reference/docbook/scheduling.xml b/src/reference/docbook/scheduling.xml
index 75b31ba03e6..61db3be7985 100644
--- a/src/reference/docbook/scheduling.xml
+++ b/src/reference/docbook/scheduling.xml
@@ -472,7 +472,7 @@ public class TaskExecutorExample {
example.
<task:scheduled-tasks scheduler="myScheduler">
- <task:scheduled ref="someObject" method="someMethod" fixed-delay="5000"/>
+ <task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/>
</task:scheduled-tasks>
<task:scheduler id="myScheduler" pool-size="10"/>
@@ -480,13 +480,19 @@ public class TaskExecutorExample {
As you can see, the scheduler is referenced by the outer element,
and each individual task includes the configuration of its trigger
metadata. In the preceding example, that metadata defines a periodic
- trigger with a fixed delay. It could also be configured with a
- "fixed-rate", or for more control, a "cron" attribute could be provided
- instead. Here's an example featuring these other options.
+ trigger with a fixed delay indicating the number of milliseconds to wait
+ after each task execution has completed. Another option is 'fixed-rate',
+ indicating how often the method should be executed regardless of how long
+ any previous execution takes. Additionally, for both fixed-delay and
+ fixed-rate tasks an 'initial-delay' parameter may be specified indicating
+ the number of milliseconds to wait before the first execution of the
+ method. For more control, a "cron" attribute may be provided instead.
+ Here is an example demonstrating these other options.
<task:scheduled-tasks scheduler="myScheduler">
- <task:scheduled ref="someObject" method="someMethod" fixed-rate="5000"/>
- <task:scheduled ref="anotherObject" method="anotherMethod" cron="*/5 * * * * MON-FRI"/>
+ <task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/>
+ <task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/>
+ <task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/>
</task:scheduled-tasks>
<task:scheduler id="myScheduler" pool-size="10"/>
@@ -523,6 +529,16 @@ public void doSomething() {
// something that should execute periodically
}
+ For fixed-delay and fixed-rate tasks, an initial delay may be
+ specified indicating the number of milliseconds to wait before the first
+ execution of the method.
+
+
+ @Scheduled(initialDelay=1000, fixedRate=5000)
+public void doSomething() {
+ // something that should execute periodically
+}
+
If simple periodic scheduling is not expressive enough, then a
cron expression may be provided. For example, the following will only
execute on weekdays.