Browse Source

@Scheduled supports "-" as cron expression value for disabled triggers

Issue: SPR-16858
pull/1918/merge
Juergen Hoeller 6 years ago
parent
commit
3a5def047f
  1. 20
      spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
  2. 26
      spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
  3. 82
      spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java

20
spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java

@ -41,6 +41,7 @@ import java.lang.annotation.Target;
* <em>composed annotations</em> with attribute overrides. * <em>composed annotations</em> with attribute overrides.
* *
* @author Mark Fisher * @author Mark Fisher
* @author Juergen Hoeller
* @author Dave Syer * @author Dave Syer
* @author Chris Beams * @author Chris Beams
* @since 3.0 * @since 3.0
@ -55,10 +56,21 @@ import java.lang.annotation.Target;
public @interface Scheduled { public @interface Scheduled {
/** /**
* A cron-like expression, extending the usual UN*X definition to include * A special cron expression value that indicates a disabled trigger: {@value}.
* triggers on the second as well as minute, hour, day of month, month * <p>This is primarily meant for use with ${...} placeholders, allowing for
* and day of week. e.g. {@code "0 * * * * MON-FRI"} means once per minute on * external disabling of corresponding scheduled methods.
* weekdays (at the top of the minute - the 0th second). * @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.
* <p>E.g. {@code "0 * * * * MON-FRI"} means once per minute on weekdays
* (at the top of the minute - the 0th second).
* <p>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 * @return an expression that can be parsed to a cron schedule
* @see org.springframework.scheduling.support.CronSequenceGenerator * @see org.springframework.scheduling.support.CronSequenceGenerator
*/ */

26
spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java

@ -105,7 +105,7 @@ public class ScheduledAnnotationBeanPostProcessor
SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean { SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, 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}.
* <p>Note that the initial lookup happens by type; this is just the fallback * <p>Note that the initial lookup happens by type; this is just the fallback
* in case of multiple scheduler beans found in the context. * in case of multiple scheduler beans found in the context.
* @since 4.2 * @since 4.2
@ -405,14 +405,16 @@ public class ScheduledAnnotationBeanPostProcessor
if (StringUtils.hasLength(cron)) { if (StringUtils.hasLength(cron)) {
Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers"); Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
processedSchedule = true; processedSchedule = true;
TimeZone timeZone; if (!Scheduled.CRON_DISABLED.equals(cron)) {
if (StringUtils.hasText(zone)) { TimeZone timeZone;
timeZone = StringUtils.parseTimeZoneString(zone); if (StringUtils.hasText(zone)) {
} timeZone = StringUtils.parseTimeZoneString(zone);
else { }
timeZone = TimeZone.getDefault(); 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 // Finally register the scheduled tasks
synchronized (this.scheduledTasks) { synchronized (this.scheduledTasks) {
Set<ScheduledTask> registeredTasks = this.scheduledTasks.get(bean); Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
if (registeredTasks == null) { regTasks.addAll(tasks);
registeredTasks = new LinkedHashSet<>(4);
this.scheduledTasks.put(bean, registeredTasks);
}
registeredTasks.addAll(tasks);
} }
} }
catch (IllegalArgumentException ex) { catch (IllegalArgumentException ex) {

82
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.TriggerContext;
import org.springframework.scheduling.config.CronTask; import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.IntervalTask; import org.springframework.scheduling.config.IntervalTask;
import org.springframework.scheduling.config.ScheduledTaskHolder;
import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger; import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.ScheduledMethodRunnable; import org.springframework.scheduling.support.ScheduledMethodRunnable;
@ -82,7 +83,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
Object postProcessor = context.getBean("postProcessor"); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertEquals(1, postProcessor.getScheduledTasks().size());
Object target = context.getBean("target"); Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@ -108,7 +111,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
Object postProcessor = context.getBean("postProcessor"); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertEquals(1, postProcessor.getScheduledTasks().size());
Object target = context.getBean("target"); Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@ -134,7 +139,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
Object postProcessor = context.getBean("postProcessor"); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertEquals(1, postProcessor.getScheduledTasks().size());
Object target = context.getBean("target"); Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@ -195,7 +202,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
Object postProcessor = context.getBean("postProcessor"); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertEquals(2, postProcessor.getScheduledTasks().size());
Object target = context.getBean("target"); Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@ -231,7 +240,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
Object postProcessor = context.getBean("postProcessor"); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertEquals(1, postProcessor.getScheduledTasks().size());
Object target = context.getBean("target"); Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@ -259,7 +270,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
Object postProcessor = context.getBean("postProcessor"); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertEquals(1, postProcessor.getScheduledTasks().size());
Object target = context.getBean("target"); Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@ -307,7 +320,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Test(expected = BeanCreationException.class) @Test(expected = BeanCreationException.class)
public void cronTaskWithMethodValidation() throws InterruptedException { public void cronTaskWithMethodValidation() {
BeanDefinition validationDefinition = new RootBeanDefinition(MethodValidationPostProcessor.class); BeanDefinition validationDefinition = new RootBeanDefinition(MethodValidationPostProcessor.class);
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(CronTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(CronTestBean.class);
@ -325,7 +338,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
Object postProcessor = context.getBean("postProcessor"); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertEquals(1, postProcessor.getScheduledTasks().size());
Object target = context.getBean("target"); Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@ -350,7 +365,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
Object postProcessor = context.getBean("postProcessor"); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertEquals(1, postProcessor.getScheduledTasks().size());
Object target = context.getBean("target"); Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) new DirectFieldAccessor( ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) new DirectFieldAccessor(
postProcessor).getPropertyValue("registrar"); postProcessor).getPropertyValue("registrar");
@ -376,7 +393,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
Object postProcessor = context.getBean("postProcessor"); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertEquals(1, postProcessor.getScheduledTasks().size());
Object target = context.getBean("target"); Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@ -407,7 +426,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
Object postProcessor = context.getBean("postProcessor"); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertEquals(1, postProcessor.getScheduledTasks().size());
Object target = context.getBean("target"); Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@ -424,6 +445,24 @@ public class ScheduledAnnotationBeanPostProcessorTests {
assertEquals(businessHoursCronExpression, task.getExpression()); 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 @Test
public void propertyPlaceholderWithFixedDelayInMillis() { public void propertyPlaceholderWithFixedDelayInMillis() {
propertyPlaceholderWithFixedDelay(false); propertyPlaceholderWithFixedDelay(false);
@ -447,7 +486,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
Object postProcessor = context.getBean("postProcessor"); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertEquals(1, postProcessor.getScheduledTasks().size());
Object target = context.getBean("target"); Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@ -488,7 +529,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
Object postProcessor = context.getBean("postProcessor"); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertEquals(1, postProcessor.getScheduledTasks().size());
Object target = context.getBean("target"); Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@ -518,7 +561,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.getBeanFactory().registerSingleton("schedules", schedules); context.getBeanFactory().registerSingleton("schedules", schedules);
context.refresh(); context.refresh();
Object postProcessor = context.getBean("postProcessor"); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertEquals(1, postProcessor.getScheduledTasks().size());
Object target = context.getBean("target"); Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@ -549,7 +594,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
Object postProcessor = context.getBean("postProcessor"); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertEquals(1, postProcessor.getScheduledTasks().size());
Object target = context.getBean("target"); Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@ -574,7 +621,9 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
Object postProcessor = context.getBean("postProcessor"); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertEquals(1, postProcessor.getScheduledTasks().size());
Object target = context.getBean("target"); Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@ -803,6 +852,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
} }
static class PropertyPlaceholderWithCronTestBean { static class PropertyPlaceholderWithCronTestBean {
@Scheduled(cron = "${schedules.businessHours}") @Scheduled(cron = "${schedules.businessHours}")

Loading…
Cancel
Save