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 c2aa1df37b..0df42573c7 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
@@ -41,6 +41,7 @@ import java.lang.annotation.Target;
* composed annotations with attribute overrides.
*
* @author Mark Fisher
+ * @author Juergen Hoeller
* @author Dave Syer
* @author Chris Beams
* @since 3.0
@@ -55,10 +56,21 @@ import java.lang.annotation.Target;
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"} means once per minute on
- * weekdays (at the top of the minute - the 0th second).
+ * A special cron expression value that indicates a disabled trigger: {@value}.
+ *
This is primarily meant for use with ${...} placeholders, allowing for
+ * external disabling of corresponding scheduled methods.
+ * @since 5.1
+ */
+ String CRON_DISABLED = "-";
+
+
+ /**
+ * 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"} means once per minute on weekdays
+ * (at the top of the minute - the 0th second).
+ *
The special value {@link #CRON_DISABLED "-"} indicates a disabled cron trigger,
+ * primarily meant for externally specified values resolved by a ${...} placeholder.
* @return an expression that can be parsed to a cron schedule
* @see org.springframework.scheduling.support.CronSequenceGenerator
*/
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 a0c57691a7..d012fd43b7 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
@@ -105,7 +105,7 @@ public class ScheduledAnnotationBeanPostProcessor
SmartInitializingSingleton, ApplicationListener, DisposableBean {
/**
- * The default name of the {@link TaskScheduler} bean to pick up: "taskScheduler".
+ * The default name of the {@link TaskScheduler} bean to pick up: {@value}.
* Note that the initial lookup happens by type; this is just the fallback
* in case of multiple scheduler beans found in the context.
* @since 4.2
@@ -405,14 +405,16 @@ public class ScheduledAnnotationBeanPostProcessor
if (StringUtils.hasLength(cron)) {
Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
processedSchedule = true;
- TimeZone timeZone;
- if (StringUtils.hasText(zone)) {
- timeZone = StringUtils.parseTimeZoneString(zone);
- }
- else {
- timeZone = TimeZone.getDefault();
+ if (!Scheduled.CRON_DISABLED.equals(cron)) {
+ TimeZone timeZone;
+ if (StringUtils.hasText(zone)) {
+ timeZone = StringUtils.parseTimeZoneString(zone);
+ }
+ else {
+ timeZone = TimeZone.getDefault();
+ }
+ tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
}
- tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
}
}
@@ -478,12 +480,8 @@ public class ScheduledAnnotationBeanPostProcessor
// Finally register the scheduled tasks
synchronized (this.scheduledTasks) {
- Set registeredTasks = this.scheduledTasks.get(bean);
- if (registeredTasks == null) {
- registeredTasks = new LinkedHashSet<>(4);
- this.scheduledTasks.put(bean, registeredTasks);
- }
- registeredTasks.addAll(tasks);
+ Set regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
+ regTasks.addAll(tasks);
}
}
catch (IllegalArgumentException ex) {
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 75693b23ed..59340eeff6 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
@@ -45,6 +45,7 @@ import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.IntervalTask;
+import org.springframework.scheduling.config.ScheduledTaskHolder;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.ScheduledMethodRunnable;
@@ -82,7 +83,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
- Object postProcessor = context.getBean("postProcessor");
+ ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
+ assertEquals(1, postProcessor.getScheduledTasks().size());
+
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@@ -108,7 +111,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
- Object postProcessor = context.getBean("postProcessor");
+ ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
+ assertEquals(1, postProcessor.getScheduledTasks().size());
+
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@@ -134,7 +139,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
- Object postProcessor = context.getBean("postProcessor");
+ ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
+ assertEquals(1, postProcessor.getScheduledTasks().size());
+
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@@ -195,7 +202,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
- Object postProcessor = context.getBean("postProcessor");
+ ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
+ assertEquals(2, postProcessor.getScheduledTasks().size());
+
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@@ -231,7 +240,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
- Object postProcessor = context.getBean("postProcessor");
+ ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
+ assertEquals(1, postProcessor.getScheduledTasks().size());
+
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@@ -259,7 +270,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
- Object postProcessor = context.getBean("postProcessor");
+ ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
+ assertEquals(1, postProcessor.getScheduledTasks().size());
+
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@@ -307,7 +320,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
}
@Test(expected = BeanCreationException.class)
- public void cronTaskWithMethodValidation() throws InterruptedException {
+ public void cronTaskWithMethodValidation() {
BeanDefinition validationDefinition = new RootBeanDefinition(MethodValidationPostProcessor.class);
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(CronTestBean.class);
@@ -325,7 +338,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
- Object postProcessor = context.getBean("postProcessor");
+ ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
+ assertEquals(1, postProcessor.getScheduledTasks().size());
+
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@@ -350,7 +365,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
- Object postProcessor = context.getBean("postProcessor");
+ ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
+ assertEquals(1, postProcessor.getScheduledTasks().size());
+
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) new DirectFieldAccessor(
postProcessor).getPropertyValue("registrar");
@@ -376,7 +393,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
- Object postProcessor = context.getBean("postProcessor");
+ ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
+ assertEquals(1, postProcessor.getScheduledTasks().size());
+
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@@ -407,7 +426,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
- Object postProcessor = context.getBean("postProcessor");
+ ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
+ assertEquals(1, postProcessor.getScheduledTasks().size());
+
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@@ -424,6 +445,24 @@ public class ScheduledAnnotationBeanPostProcessorTests {
assertEquals(businessHoursCronExpression, task.getExpression());
}
+ @Test
+ public void propertyPlaceholderWithInactiveCron() {
+ String businessHoursCronExpression = "-";
+ BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
+ BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertyPlaceholderConfigurer.class);
+ Properties properties = new Properties();
+ properties.setProperty("schedules.businessHours", businessHoursCronExpression);
+ placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties);
+ BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithCronTestBean.class);
+ context.registerBeanDefinition("postProcessor", processorDefinition);
+ context.registerBeanDefinition("placeholder", placeholderDefinition);
+ context.registerBeanDefinition("target", targetDefinition);
+ context.refresh();
+
+ ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
+ assertTrue(postProcessor.getScheduledTasks().isEmpty());
+ }
+
@Test
public void propertyPlaceholderWithFixedDelayInMillis() {
propertyPlaceholderWithFixedDelay(false);
@@ -447,7 +486,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
- Object postProcessor = context.getBean("postProcessor");
+ ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
+ assertEquals(1, postProcessor.getScheduledTasks().size());
+
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@@ -488,7 +529,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
- Object postProcessor = context.getBean("postProcessor");
+ ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
+ assertEquals(1, postProcessor.getScheduledTasks().size());
+
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@@ -518,7 +561,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.getBeanFactory().registerSingleton("schedules", schedules);
context.refresh();
- Object postProcessor = context.getBean("postProcessor");
+ ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
+ assertEquals(1, postProcessor.getScheduledTasks().size());
+
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@@ -549,7 +594,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
- Object postProcessor = context.getBean("postProcessor");
+ ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
+ assertEquals(1, postProcessor.getScheduledTasks().size());
+
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@@ -574,7 +621,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
- Object postProcessor = context.getBean("postProcessor");
+ ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
+ assertEquals(1, postProcessor.getScheduledTasks().size());
+
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@@ -803,6 +852,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
}
}
+
static class PropertyPlaceholderWithCronTestBean {
@Scheduled(cron = "${schedules.businessHours}")