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 19896362fb..dea788ce08 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 @@ -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 @@ -69,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 5343d13ced..f5f236d2f9 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 @@ -34,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; @@ -75,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(); /** @@ -146,19 +142,20 @@ public class ScheduledAnnotationBeanPostProcessor 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); } @@ -175,16 +172,6 @@ public class ScheduledAnnotationBeanPostProcessor Map configurers = this.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); - if (this.scheduler != null) { this.registrar.setScheduler(this.scheduler); } @@ -193,7 +180,7 @@ public class ScheduledAnnotationBeanPostProcessor configurer.configureTasks(this.registrar); } - if (this.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)); 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 761f151f38..095ff700a7 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 @@ -38,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 0000000000..cf859ab427 --- /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 0000000000..4b39927e8f --- /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 a3708a5b85..b5b0b44dde 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 @@ -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,13 +56,13 @@ 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>(); @@ -97,11 +100,25 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean 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,6 +148,19 @@ 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; } @@ -126,6 +169,19 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean * @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; } @@ -134,20 +190,37 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean * @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) { + public void addCronTask(Runnable task, String expression) { + this.addCronTask(new CronTask(task, expression)); + } + + /** + * Add a {@link CronTask}. + * @since 3.2 + */ + public void addCronTask(CronTask task) { if (this.cronTasks == null) { - this.cronTasks = new HashMap(); + this.cronTasks = new ArrayList(); } - this.cronTasks.put(task, cronExpression); + this.cronTasks.add(task); } /** @@ -155,10 +228,19 @@ 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); } /** @@ -166,10 +248,30 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean * @see TaskScheduler#scheduleWithFixedDelay(Runnable, long) */ 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 HashMap(); + this.fixedDelayTasks = new ArrayList(); } - this.fixedDelayTasks.put(task, delay); + 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()); } /** @@ -177,28 +279,48 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean * #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())); + } } } } 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 52093b354e..828528afab 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 @@ -19,7 +19,7 @@ package org.springframework.scheduling.config; 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; @@ -32,11 +32,13 @@ 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,54 +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( "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) { @@ -119,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 0000000000..02dac4cba4 --- /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 0000000000..6d0aa6c813 --- /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 2e1526ac66..b1753f9275 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 a3c8fc8df0..f23b327cf8 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 3091b0ba07..cb05f8fcd3 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 @@ -81,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 @@ -105,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); } @@ -130,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 @@ -154,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 @@ -184,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 @@ -214,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) @@ -237,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) 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 776b5108f5..5e8f75a358 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 @@ -17,9 +17,8 @@ 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 org.junit.Before; import org.junit.Test; @@ -31,10 +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 { @@ -64,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(); @@ -76,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 30e46c7bb8..48f5ebdb07 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 92ce99c761..64b4ee18a9 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 93ea81dfa3..61db3be798 100644 --- a/src/reference/docbook/scheduling.xml +++ b/src/reference/docbook/scheduling.xml @@ -483,11 +483,14 @@ public class TaskExecutorExample { 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. For more control, a "cron" attribute may be - provided instead. Here is an example demonstrating these other options. - + 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="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> @@ -526,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.