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 ccda008fcc855a6a5f7cc50fe738e50aa3491451..19896362fb7d22b896a8aca71b04e0113c86ba61 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,11 +24,11 @@ import java.lang.annotation.Target; /** * Annotation that marks a method to be scheduled. Exactly one of the - * cron, fixedDelay, or fixedRate - * attributes must be provided. + * {@link #cron()}, {@link #fixedDelay()}, or {@link #fixedRate()} + * attributes must be specified. * *

The annotated method must expect no arguments and have a - * void return type. + * {@code void} return type. * *

Processing of {@code @Scheduled} annotations is performed by * registering a {@link ScheduledAnnotationBeanPostProcessor}. This can be @@ -49,9 +49,10 @@ public @interface Scheduled { /** * A cron-like expression, extending the usual UN*X definition to include * triggers on the second as well as minute, hour, day of month, month - * and day of week. e.g. "0 * * * * MON-FRI" means once - * per minute on weekdays (at the top of the minute - the 0th second). + * and day of week. e.g. {@code "0 * * * * MON-FRI"} means once per minute on + * weekdays (at the top of the minute - the 0th second). * @return an expression that can be parsed to a cron schedule + * @see org.springframework.scheduling.support.CronSequenceGenerator */ String cron() default ""; 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 692270044cea62a967ef8f1e1c384ba2aeb3c98e..5343d13ced36a908a2f6567e943eca7163f639d6 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.scheduling.annotation; import java.lang.reflect.Method; + import java.util.HashMap; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; @@ -104,7 +105,6 @@ public class ScheduledAnnotationBeanPostProcessor return LOWEST_PRECEDENCE; } - public Object postProcessBeforeInitialization(Object bean, String beanName) { return bean; } @@ -124,9 +124,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,7 +139,7 @@ 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; @@ -170,7 +172,8 @@ public class ScheduledAnnotationBeanPostProcessor return; } - Map configurers = applicationContext.getBeansOfType(SchedulingConfigurer.class); + Map configurers = + this.applicationContext.getBeansOfType(SchedulingConfigurer.class); if (this.cronTasks.isEmpty() && this.fixedDelayTasks.isEmpty() && this.fixedRateTasks.isEmpty() && configurers.isEmpty()) { @@ -190,19 +193,23 @@ public class ScheduledAnnotationBeanPostProcessor configurer.configureTasks(this.registrar); } - if (registrar.getScheduler() == null) { + if (this.registrar.getScheduler() == null) { Map schedulers = new HashMap(); schedulers.putAll(applicationContext.getBeansOfType(TaskScheduler.class)); schedulers.putAll(applicationContext.getBeansOfType(ScheduledExecutorService.class)); if (schedulers.size() == 0) { // do nothing -> fall back to default scheduler - } else if (schedulers.size() == 1) { + } + else if (schedulers.size() == 1) { this.registrar.setScheduler(schedulers.values().iterator().next()); - } else if (schedulers.size() >= 2){ - throw new IllegalStateException("More than one TaskScheduler and/or ScheduledExecutorService " + - "exist within the context. Remove all but one of the beans; or implement the " + - "SchedulingConfigurer interface and call ScheduledTaskRegistrar#setScheduler " + - "explicitly within the configureTasks() callback. Found the following beans: " + schedulers.keySet()); + } + else if (schedulers.size() >= 2){ + throw new IllegalStateException( + "More than one TaskScheduler and/or ScheduledExecutorService " + + "exist within the context. Remove all but one of the beans; or " + + "implement the SchedulingConfigurer interface and call " + + "ScheduledTaskRegistrar#setScheduler explicitly within the " + + "configureTasks() callback. Found the following beans: " + schedulers.keySet()); } } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/SchedulingConfigurer.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/SchedulingConfigurer.java index d4787c0e4cce07bd420119a32b87a970c5d7d545..761f151f384edeef46aef718795354b6589e745c 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/SchedulingConfigurer.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/SchedulingConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,15 @@ package org.springframework.scheduling.annotation; import org.springframework.scheduling.config.ScheduledTaskRegistrar; /** - * Interface to be implemented by @{@link org.springframework.context.annotation.Configuration} - * classes annotated with @{@link EnableScheduling} that wish to register scheduled tasks - * in a programmatic fashion as opposed to the declarative approach of - * using the @{@link Scheduled} annotation. For example, this may be necessary when - * implementing {@link org.springframework.scheduling.Trigger Trigger}-based tasks, which - * are not supported by the {@code @Scheduled} annotation. + * Optional interface to be implemented by @{@link + * org.springframework.context.annotation.Configuration Configuration} classes annotated + * with @{@link EnableScheduling}. Typically used for setting a specific + * {@link org.springframework.scheduling.TaskScheduler TaskScheduler} bean to be used when + * executing scheduled tasks or for registering scheduled tasks in a programmatic + * fashion as opposed to the declarative approach of using the @{@link Scheduled} + * annotation. For example, this may be necessary when implementing {@link + * org.springframework.scheduling.Trigger Trigger}-based tasks, which are not supported by + * the {@code @Scheduled} annotation. * *

See @{@link EnableScheduling} for detailed usage examples. * 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 1898441e21726b1a3652b2c0aff20e26614efbbb..a3708a5b85b213d5e6ead9933a624f1aff62d51d 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java +++ b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,7 +65,7 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean /** - * 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 +73,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,7 +91,7 @@ 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; @@ -121,6 +121,14 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean this.fixedRateTasks = fixedRateTasks; } + /** + * Specify triggered tasks as a Map of Runnables (the tasks) and fixed-delay values. + * @see TaskScheduler#scheduleWithFixedDelay(Runnable, long) + */ + public void setFixedDelayTasks(Map fixedDelayTasks) { + this.fixedDelayTasks = fixedDelayTasks; + } + /** * Add a Runnable task to be triggered per the given {@link Trigger}. * @see TaskScheduler#scheduleAtFixedRate(Runnable, long) @@ -142,17 +150,6 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean this.cronTasks.put(task, cronExpression); } - /** - * Add a Runnable task to be triggered with the given fixed delay. - * @see TaskScheduler#scheduleWithFixedDelay(Runnable, long) - */ - public void addFixedDelayTask(Runnable task, long delay) { - if (this.fixedDelayTasks == null) { - this.fixedDelayTasks = new HashMap(); - } - this.fixedDelayTasks.put(task, delay); - } - /** * Add a Runnable task to be triggered at the given fixed-rate period. * @see TaskScheduler#scheduleAtFixedRate(Runnable, long) @@ -165,14 +162,20 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean } /** - * Specify triggered tasks as a Map of Runnables (the tasks) and fixed-delay values. + * Add a Runnable task to be triggered with the given fixed delay. * @see TaskScheduler#scheduleWithFixedDelay(Runnable, long) */ - public void setFixedDelayTasks(Map fixedDelayTasks) { - this.fixedDelayTasks = fixedDelayTasks; + public void addFixedDelayTask(Runnable task, long delay) { + if (this.fixedDelayTasks == null) { + this.fixedDelayTasks = new HashMap(); + } + this.fixedDelayTasks.put(task, delay); } - + /** + * Schedule all registered tasks against the underlying {@linkplain + * #setTaskScheduler(TaskScheduler) task scheduler. + */ public void afterPropertiesSet() { if (this.taskScheduler == null) { this.localExecutor = Executors.newSingleThreadScheduledExecutor(); @@ -200,7 +203,6 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean } } - public void destroy() { for (ScheduledFuture future : this.scheduledFutures) { future.cancel(true); diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParser.java b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParser.java index 6e29ad4f10275343f7ffc278a56c3134064a5b97..52093b354e34393549a84020dc82fc5d69459c06 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParser.java +++ b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,6 @@ 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; @@ -28,6 +24,10 @@ 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. * @@ -87,9 +87,8 @@ public class ScheduledTasksBeanDefinitionParser extends AbstractSingleBeanDefini 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) { diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java index 9b98b48929b4c1df81a946226d546501ac392a57..1d7abd9b2bdf85bcdb314e5d994e86b56396bc4b 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,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; @@ -34,6 +30,10 @@ import org.springframework.scheduling.TriggerContext; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.config.ScheduledTaskRegistrar; +import static org.hamcrest.Matchers.*; + +import static org.junit.Assert.*; + /** * Tests use of @EnableScheduling on @Configuration classes. * diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java index f1ffde04117f719470035f8bf429eb59acaa6053..81ca20bdea22b9be0f1c2392009e8c1591a0bf5f 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import java.lang.reflect.Method; import java.util.Map; import java.util.Properties; -import static org.junit.Assert.*; import org.junit.Test; import org.springframework.beans.DirectFieldAccessor; @@ -37,6 +36,8 @@ import org.springframework.context.support.StaticApplicationContext; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.support.ScheduledMethodRunnable; +import static org.junit.Assert.*; + /** * @author Mark Fisher * @author Juergen Hoeller @@ -269,7 +270,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } - public static class FixedDelayTestBean { + static class FixedDelayTestBean { @Scheduled(fixedDelay=5000) public void fixedDelay() { @@ -277,7 +278,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } - public static class FixedRateTestBean { + static class FixedRateTestBean { @Scheduled(fixedRate=3000) public void fixedRate() { @@ -285,7 +286,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 +304,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } - private static class EmptyAnnotationTestBean { + static class EmptyAnnotationTestBean { @Scheduled public void invalid() { @@ -304,7 +313,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } - private static class InvalidCronTestBean { + static class InvalidCronTestBean { @Scheduled(cron="abc") public void invalid() { @@ -313,7 +322,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } - private static class NonVoidReturnTypeTestBean { + static class NonVoidReturnTypeTestBean { @Scheduled(fixedRate=3000) public String invalid() { @@ -323,7 +332,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } - private static class NonEmptyParamListTestBean { + static class NonEmptyParamListTestBean { @Scheduled(fixedRate=3000) public void invalid(String oops) { @@ -344,7 +353,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { private static @interface Hourly {} - private static class MetaAnnotationFixedRateTestBean { + static class MetaAnnotationFixedRateTestBean { @EveryFiveSeconds public void checkForUpdates() { @@ -352,7 +361,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } - private static class MetaAnnotationCronTestBean { + static class MetaAnnotationCronTestBean { @Hourly public void generateReport() { @@ -360,7 +369,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } - private static class PropertyPlaceholderTestBean { + static class PropertyPlaceholderTestBean { @Scheduled(cron = "${schedules.businessHours}") public void x() { @@ -370,11 +379,11 @@ public class ScheduledAnnotationBeanPostProcessorTests { @Scheduled(cron = "${schedules.businessHours}") @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) + @Retention(RetentionPolicy.RUNTIME) private static @interface BusinessHours {} - private static class PropertyPlaceholderMetaAnnotationTestBean { + static class PropertyPlaceholderMetaAnnotationTestBean { @BusinessHours public void y() { diff --git a/spring-context/src/test/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParserTests.java b/spring-context/src/test/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParserTests.java index f24355093f9d3071c480575f03c8c24f9e11cd29..776b5108f5ae1aca375c53f96f4d0c7a31a4047b 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParserTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,6 @@ import java.util.Collection; import java.util.Date; import java.util.Map; -import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; @@ -32,6 +31,8 @@ import org.springframework.scheduling.Trigger; import org.springframework.scheduling.TriggerContext; import org.springframework.scheduling.support.ScheduledMethodRunnable; +import static org.junit.Assert.*; + /** * @author Mark Fisher */ diff --git a/src/reference/docbook/scheduling.xml b/src/reference/docbook/scheduling.xml index 75b31ba03e6fd6312f2007d554380ffb3c081a3f..93ea81dfa30368d10b8972c469850a996963032f 100644 --- a/src/reference/docbook/scheduling.xml +++ b/src/reference/docbook/scheduling.xml @@ -472,7 +472,7 @@ public class TaskExecutorExample { example. <task:scheduled-tasks scheduler="myScheduler"> - <task:scheduled ref="someObject" method="someMethod" fixed-delay="5000"/> + <task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/> </task:scheduled-tasks> <task:scheduler id="myScheduler" pool-size="10"/> @@ -480,13 +480,16 @@ public class TaskExecutorExample { As you can see, the scheduler is referenced by the outer element, and each individual task includes the configuration of its trigger metadata. In the preceding example, that metadata defines a periodic - trigger with a fixed delay. It could also be configured with a - "fixed-rate", or for more control, a "cron" attribute could be provided - instead. Here's an example featuring these other options. + trigger with a fixed delay indicating the number of milliseconds to wait + after each task execution has completed. Another option is 'fixed-rate', + indicating how often the method should be executed regardless of how long + any previous execution takes. For more control, a "cron" attribute may be + provided instead. Here is an example demonstrating these other options. + <task:scheduled-tasks scheduler="myScheduler"> - <task:scheduled ref="someObject" method="someMethod" fixed-rate="5000"/> - <task:scheduled ref="anotherObject" method="anotherMethod" cron="*/5 * * * * MON-FRI"/> + <task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/> + <task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/> </task:scheduled-tasks> <task:scheduler id="myScheduler" pool-size="10"/>