提交 5330c52e 编写于 作者: C Chris Beams

Merge branch cbeams/SPR-7022

* SPR-7022:
  Support initial delay attribute for scheduled tasks
  Polish scheduled task execution infrastructure
/*
* 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
* <code>cron</code>, <code>fixedDelay</code>, or <code>fixedRate</code>
* attributes must be provided.
* {@link #cron()}, {@link #fixedDelay()}, or {@link #fixedRate()}
* attributes must be specified.
*
* <p>The annotated method must expect no arguments and have a
* <code>void</code> return type.
* {@code void} return type.
*
* <p>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. <code>"0 * * * * MON-FRI"</code> 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;
}
/*
* 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<Runnable, String> cronTasks = new HashMap<Runnable, String>();
private final Map<Runnable, Long> fixedDelayTasks = new HashMap<Runnable, Long>();
private final Map<Runnable, Long> fixedRateTasks = new HashMap<Runnable, Long>();
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<String, SchedulingConfigurer> 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<String, SchedulingConfigurer> 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<String, ? super Object> schedulers = new HashMap<String, Object>();
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());
}
}
......
/*
* 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 <em>programmatic</em> fashion as opposed to the <em>declarative</em> 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 <em>programmatic</em>
* fashion as opposed to the <em>declarative</em> 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.
*
* <p>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);
}
/*
* 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;
}
}
/*
* 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;
}
}
/*
* 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.
*
* <p>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<Runnable, Trigger> triggerTasks;
private List<TriggerTask> triggerTasks;
private Map<Runnable, String> cronTasks;
private List<CronTask> cronTasks;
private Map<Runnable, Long> fixedRateTasks;
private List<IntervalTask> fixedRateTasks;
private Map<Runnable, Long> fixedDelayTasks;
private List<IntervalTask> fixedDelayTasks;
private final Set<ScheduledFuture<?>> scheduledFutures = new LinkedHashSet<ScheduledFuture<?>>();
/**
* 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<Runnable, Trigger> triggerTasks) {
this.triggerTasks = new ArrayList<TriggerTask>();
for (Map.Entry<Runnable, Trigger> 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 <task:*>} namespace parsing.
* @since 3.2
* @see ScheduledTasksBeanDefinitionParser
*/
public void setTriggerTasksList(List<TriggerTask> triggerTasks) {
this.triggerTasks = triggerTasks;
}
......@@ -110,6 +127,19 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean
* @see CronTrigger
*/
public void setCronTasks(Map<Runnable, String> cronTasks) {
this.cronTasks = new ArrayList<CronTask>();
for (Map.Entry<Runnable, String> task : cronTasks.entrySet()) {
this.addCronTask(task.getKey(), task.getValue());
}
}
/**
* Specify triggered tasks as a list of {@link CronTask} objects. Primarily used by
* {@code <task:*>} namespace parsing.
* @since 3.2
* @see ScheduledTasksBeanDefinitionParser
*/
public void setCronTasksList(List<CronTask> cronTasks) {
this.cronTasks = cronTasks;
}
......@@ -118,39 +148,79 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean
* @see TaskScheduler#scheduleAtFixedRate(Runnable, long)
*/
public void setFixedRateTasks(Map<Runnable, Long> fixedRateTasks) {
this.fixedRateTasks = new ArrayList<IntervalTask>();
for (Map.Entry<Runnable, Long> task : fixedRateTasks.entrySet()) {
this.addFixedRateTask(task.getKey(), task.getValue());
}
}
/**
* Specify fixed-rate tasks as a list of {@link IntervalTask} objects. Primarily used
* by {@code <task:*>} namespace parsing.
* @since 3.2
* @see ScheduledTasksBeanDefinitionParser
*/
public void setFixedRateTasksList(List<IntervalTask> 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<Runnable, Long> fixedDelayTasks) {
this.fixedDelayTasks = new ArrayList<IntervalTask>();
for (Map.Entry<Runnable, Long> task : fixedDelayTasks.entrySet()) {
this.addFixedDelayTask(task.getKey(), task.getValue());
}
}
/**
* Specify fixed-delay tasks as a list of {@link IntervalTask} objects. Primarily used
* by {@code <task:*>} namespace parsing.
* @since 3.2
* @see ScheduledTasksBeanDefinitionParser
*/
public void setFixedDelayTasksList(List<IntervalTask> 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<Runnable, Trigger>();
this.triggerTasks = new ArrayList<TriggerTask>();
}
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<Runnable, String>();
}
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<Runnable, Long>();
public void addCronTask(CronTask task) {
if (this.cronTasks == null) {
this.cronTasks = new ArrayList<CronTask>();
}
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<Runnable, Long>();
this.fixedRateTasks = new ArrayList<IntervalTask>();
}
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<Runnable, Long> 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<IntervalTask>();
}
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<Runnable, Trigger> 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<Runnable, String> 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<Runnable, Long> 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<Runnable, Long> 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);
......
/*
* 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<RuntimeBeanReference, String> cronTaskMap = new ManagedMap<RuntimeBeanReference, String>();
ManagedMap<RuntimeBeanReference, String> fixedDelayTaskMap = new ManagedMap<RuntimeBeanReference, String>();
ManagedMap<RuntimeBeanReference, String> fixedRateTaskMap = new ManagedMap<RuntimeBeanReference, String>();
ManagedMap<RuntimeBeanReference, RuntimeBeanReference> triggerTaskMap = new ManagedMap<RuntimeBeanReference, RuntimeBeanReference>();
ManagedList<RuntimeBeanReference> cronTaskList = new ManagedList<RuntimeBeanReference>();
ManagedList<RuntimeBeanReference> fixedDelayTaskList = new ManagedList<RuntimeBeanReference>();
ManagedList<RuntimeBeanReference> fixedRateTaskList = new ManagedList<RuntimeBeanReference>();
ManagedList<RuntimeBeanReference> triggerTaskList = new ManagedList<RuntimeBeanReference>();
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);
}
}
/*
* 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;
}
}
/*
* 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;
}
}
......@@ -339,6 +339,10 @@ public class CronSequenceGenerator {
return result;
}
String getExpression() {
return this.expression;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof CronSequenceGenerator)) {
......
......@@ -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) {
......
......@@ -229,6 +229,14 @@
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="initial-delay" type="xsd:int" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Number of milliseconds to delay before the first execution of a 'fixed-rate' or
'fixed-delay' task.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="ref" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
......
/*
* 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,15 +16,11 @@
package org.springframework.scheduling.annotation;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertThat;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -32,8 +28,13 @@ import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.IntervalTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
* Tests use of @EnableScheduling on @Configuration classes.
*
......@@ -383,6 +384,45 @@ public class EnableSchedulingTests {
}
@Test
public void withTaskAddedVia_configureTasks() throws InterruptedException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(SchedulingEnabled_withTaskAddedVia_configureTasks.class);
ctx.refresh();
Thread.sleep(20);
ThreadAwareWorker worker = ctx.getBean(ThreadAwareWorker.class);
ctx.close();
assertThat(worker.executedByThread, startsWith("taskScheduler-"));
}
@Configuration
@EnableScheduling
static class SchedulingEnabled_withTaskAddedVia_configureTasks implements SchedulingConfigurer {
@Bean
public ThreadAwareWorker worker() {
return new ThreadAwareWorker();
}
@Bean
public TaskScheduler taskScheduler() {
return new ThreadPoolTaskScheduler();
}
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskScheduler());
taskRegistrar.addFixedRateTask(new IntervalTask(
new Runnable() {
public void run() {
worker().executedByThread = Thread.currentThread().getName();
}
},
10, 0));
}
}
@Test
public void withTriggerTask() throws InterruptedException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
......@@ -421,4 +461,34 @@ public class EnableSchedulingTests {
return scheduler;
}
}
@Test
public void withInitiallyDelayedFixedRateTask() throws InterruptedException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(FixedRateTaskConfig_withInitialDelay.class);
ctx.refresh();
Thread.sleep(1950);
AtomicInteger counter = ctx.getBean(AtomicInteger.class);
ctx.close();
assertThat(counter.get(), greaterThan(0)); // the @Scheduled method was called
assertThat(counter.get(), lessThanOrEqualTo(10)); // but not more than times the delay allows
}
@EnableScheduling @Configuration
static class FixedRateTaskConfig_withInitialDelay {
@Bean
public AtomicInteger counter() {
return new AtomicInteger();
}
@Scheduled(initialDelay=1000, fixedRate=100)
public void task() {
counter().incrementAndGet();
}
}
}
/*
* 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.
......@@ -22,10 +22,9 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.List;
import java.util.Properties;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
......@@ -34,12 +33,17 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.support.StaticApplicationContext;
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 static org.junit.Assert.*;
/**
* @author Mark Fisher
* @author Juergen Hoeller
* @author Chris Beams
*/
public class ScheduledAnnotationBeanPostProcessorTests {
......@@ -56,15 +60,18 @@ public class ScheduledAnnotationBeanPostProcessorTests {
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
Map<Runnable, Long> fixedDelayTasks = (Map<Runnable, Long>)
@SuppressWarnings("unchecked")
List<IntervalTask> fixedDelayTasks = (List<IntervalTask>)
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<Runnable, Long> fixedRateTasks = (Map<Runnable, Long>)
@SuppressWarnings("unchecked")
List<IntervalTask> fixedRateTasks = (List<IntervalTask>)
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<IntervalTask> fixedRateTasks = (List<IntervalTask>)
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<Runnable, String> cronTasks = (Map<Runnable, String>)
@SuppressWarnings("unchecked")
List<CronTask> cronTasks = (List<CronTask>)
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<Runnable, Long> fixedRateTasks = (Map<Runnable, Long>)
@SuppressWarnings("unchecked")
List<IntervalTask> fixedRateTasks = (List<IntervalTask>)
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<Runnable, String> cronTasks = (Map<Runnable, String>)
@SuppressWarnings("unchecked")
List<CronTask> cronTasks = (List<CronTask>)
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<Runnable, String> cronTasks = (Map<Runnable, String>)
@SuppressWarnings("unchecked")
List<CronTask> cronTasks = (List<CronTask>)
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<Runnable, String> cronTasks = (Map<Runnable, String>)
@SuppressWarnings("unchecked")
List<CronTask> cronTasks = (List<CronTask>)
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() {
......
/*
* 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<Runnable, Long> tasks = (Map<Runnable, Long>) new DirectFieldAccessor(
List<IntervalTask> tasks = (List<IntervalTask>) 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<Runnable, Long> tasks = (Map<Runnable, Long>) new DirectFieldAccessor(
List<IntervalTask> tasks = (List<IntervalTask>) new DirectFieldAccessor(
this.registrar).getPropertyValue("fixedRateTasks");
assertEquals(2, tasks.size());
Collection<Long> 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<Runnable, Long> tasks = (Map<Runnable, Long>) new DirectFieldAccessor(
List<IntervalTask> tasks = (List<IntervalTask>) 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<Runnable, String> tasks = (Map<Runnable, String>) new DirectFieldAccessor(
List<CronTask> tasks = (List<CronTask>) 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<Runnable, Trigger> tasks = (Map<Runnable, Trigger>) new DirectFieldAccessor(
List<TriggerTask> tasks = (List<TriggerTask>) 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));
}
......
......@@ -11,8 +11,10 @@
<task:scheduled ref="testBean" method="test" fixed-rate="1000"/>
<task:scheduled ref="testBean" method="test" fixed-rate="2000"/>
<task:scheduled ref="testBean" method="test" fixed-delay="3000"/>
<task:scheduled ref="testBean" method="test" fixed-delay="3500" initial-delay="250"/>
<task:scheduled ref="testBean" method="test" cron="*/4 * 9-17 * * MON-FRI"/>
<task:scheduled ref="testBean" method="test" trigger="customTrigger"/>
<task:scheduled ref="testBean" method="test" fixed-rate="4000" initial-delay="500"/>
</task:scheduled-tasks>
<task:scheduler id="testScheduler"/>
......
......@@ -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)
-------------------------------------
......
......@@ -472,7 +472,7 @@ public class TaskExecutorExample {
example.</para>
<programlisting language="xml">&lt;task:scheduled-tasks scheduler="myScheduler"&gt;
&lt;task:scheduled ref="someObject" method="someMethod" fixed-delay="5000"/&gt;
&lt;task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/&gt;
&lt;/task:scheduled-tasks&gt;
&lt;task:scheduler id="myScheduler" pool-size="10"/&gt;</programlisting>
......@@ -480,13 +480,19 @@ public class TaskExecutorExample {
<para>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.</para>
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.</para>
<programlisting language="xml">&lt;task:scheduled-tasks scheduler="myScheduler"&gt;
&lt;task:scheduled ref="someObject" method="someMethod" fixed-rate="5000"/&gt;
&lt;task:scheduled ref="anotherObject" method="anotherMethod" cron="*/5 * * * * MON-FRI"/&gt;
&lt;task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/&gt;
&lt;task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/&gt;
&lt;task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/&gt;
&lt;/task:scheduled-tasks&gt;
&lt;task:scheduler id="myScheduler" pool-size="10"/&gt;</programlisting>
......@@ -523,6 +529,16 @@ public void doSomething() {
// something that should execute periodically
}</programlisting>
<para>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.
</para>
<programlisting language="java">@Scheduled(initialDelay=1000, fixedRate=5000)
public void doSomething() {
// something that should execute periodically
}</programlisting>
<para>If simple periodic scheduling is not expressive enough, then a
cron expression may be provided. For example, the following will only
execute on weekdays.</para>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册