Browse Source

Support TimeUnit in the @Scheduled annotation

This commit introduces a new `timeUnit` attribute in the @Scheduled
annotation to allow the user to specify a time unit other than
milliseconds.

Closes gh-27309
pull/27330/head
Axzial 3 years ago committed by Sam Brannen
parent
commit
e99b43b91e
  1. 36
      spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
  2. 15
      spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
  3. 216
      spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java

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

@ -22,6 +22,7 @@ import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.config.ScheduledTaskRegistrar;
@ -46,6 +47,7 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Dave Syer * @author Dave Syer
* @author Chris Beams * @author Chris Beams
* @author Victor Brown
* @since 3.0 * @since 3.0
* @see EnableScheduling * @see EnableScheduling
* @see ScheduledAnnotationBeanPostProcessor * @see ScheduledAnnotationBeanPostProcessor
@ -101,52 +103,64 @@ public @interface Scheduled {
String zone() default ""; String zone() default "";
/** /**
* Execute the annotated method with a fixed period in milliseconds between the * Execute the annotated method with a fixed period between the
* end of the last invocation and the start of the next. * end of the last invocation and the start of the next.
* @return the delay in milliseconds * Using milliseconds by default with timeUnit().
* @return the delay
*/ */
long fixedDelay() default -1; long fixedDelay() default -1;
/** /**
* Execute the annotated method with a fixed period in milliseconds between the * Execute the annotated method with a fixed period between the
* end of the last invocation and the start of the next. * end of the last invocation and the start of the next.
* @return the delay in milliseconds as a String value, e.g. a placeholder * Using milliseconds by default with fixedDelayTimeUnit().
* @return the delay as a String value, e.g. a placeholder
* or a {@link java.time.Duration#parse java.time.Duration} compliant value * or a {@link java.time.Duration#parse java.time.Duration} compliant value
* @since 3.2.2 * @since 3.2.2
*/ */
String fixedDelayString() default ""; String fixedDelayString() default "";
/** /**
* Execute the annotated method with a fixed period in milliseconds between * Execute the annotated method with a fixed period between
* invocations. * invocations.
* @return the period in milliseconds * Using milliseconds by default with timeUnit().
* @return the period
*/ */
long fixedRate() default -1; long fixedRate() default -1;
/** /**
* Execute the annotated method with a fixed period in milliseconds between * Execute the annotated method with a fixed period between
* invocations. * invocations.
* @return the period in milliseconds as a String value, e.g. a placeholder * Using milliseconds by default with fixedRateTimeUnit().
* @return the period as a String value, e.g. a placeholder
* or a {@link java.time.Duration#parse java.time.Duration} compliant value * or a {@link java.time.Duration#parse java.time.Duration} compliant value
* @since 3.2.2 * @since 3.2.2
*/ */
String fixedRateString() default ""; String fixedRateString() default "";
/** /**
* Number of milliseconds to delay before the first execution of a * Number to delay before the first execution of a
* {@link #fixedRate} or {@link #fixedDelay} task. * {@link #fixedRate} or {@link #fixedDelay} task.
* @return the initial delay in milliseconds * Using milliseconds by default with timeUnit().
* @return the initial
* @since 3.2 * @since 3.2
*/ */
long initialDelay() default -1; long initialDelay() default -1;
/** /**
* Number of milliseconds to delay before the first execution of a * Number to delay before the first execution of a
* {@link #fixedRate} or {@link #fixedDelay} task. * {@link #fixedRate} or {@link #fixedDelay} task.
* Using milliseconds by default with initialDelayTimeUnit().
* @return the initial delay in milliseconds as a String value, e.g. a placeholder * @return the initial delay in milliseconds as a String value, e.g. a placeholder
* or a {@link java.time.Duration#parse java.time.Duration} compliant value * or a {@link java.time.Duration#parse java.time.Duration} compliant value
* @since 3.2.2 * @since 3.2.2
*/ */
String initialDelayString() default ""; String initialDelayString() default "";
/**
* Specify the {@link TimeUnit} to use for initialDelay, fixedRate and fixedDelay values.
* @return the {@link TimeUnit}, by default milliseconds will be used.
*/
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
} }

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

@ -30,6 +30,7 @@ import java.util.Set;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@ -94,6 +95,7 @@ import org.springframework.util.StringValueResolver;
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Chris Beams * @author Chris Beams
* @author Elizabeth Chatman * @author Elizabeth Chatman
* @author Victor Brown
* @since 3.0 * @since 3.0
* @see Scheduled * @see Scheduled
* @see EnableScheduling * @see EnableScheduling
@ -398,7 +400,7 @@ public class ScheduledAnnotationBeanPostProcessor
Set<ScheduledTask> tasks = new LinkedHashSet<>(4); Set<ScheduledTask> tasks = new LinkedHashSet<>(4);
// Determine initial delay // Determine initial delay
long initialDelay = scheduled.initialDelay(); long initialDelay = TimeUnit.MILLISECONDS.convert(scheduled.initialDelay(), scheduled.timeUnit());
String initialDelayString = scheduled.initialDelayString(); String initialDelayString = scheduled.initialDelayString();
if (StringUtils.hasText(initialDelayString)) { if (StringUtils.hasText(initialDelayString)) {
Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both"); Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
@ -407,7 +409,7 @@ public class ScheduledAnnotationBeanPostProcessor
} }
if (StringUtils.hasLength(initialDelayString)) { if (StringUtils.hasLength(initialDelayString)) {
try { try {
initialDelay = parseDelayAsLong(initialDelayString); initialDelay = TimeUnit.MILLISECONDS.convert(parseDelayAsLong(initialDelayString), scheduled.timeUnit());
} }
catch (RuntimeException ex) { catch (RuntimeException ex) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
@ -446,12 +448,13 @@ public class ScheduledAnnotationBeanPostProcessor
} }
// Check fixed delay // Check fixed delay
long fixedDelay = scheduled.fixedDelay(); long fixedDelay = TimeUnit.MILLISECONDS.convert(scheduled.fixedDelay(), scheduled.timeUnit());
if (fixedDelay >= 0) { if (fixedDelay >= 0) {
Assert.isTrue(!processedSchedule, errorMessage); Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true; processedSchedule = true;
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay))); tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
} }
String fixedDelayString = scheduled.fixedDelayString(); String fixedDelayString = scheduled.fixedDelayString();
if (StringUtils.hasText(fixedDelayString)) { if (StringUtils.hasText(fixedDelayString)) {
if (this.embeddedValueResolver != null) { if (this.embeddedValueResolver != null) {
@ -461,7 +464,7 @@ public class ScheduledAnnotationBeanPostProcessor
Assert.isTrue(!processedSchedule, errorMessage); Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true; processedSchedule = true;
try { try {
fixedDelay = parseDelayAsLong(fixedDelayString); fixedDelay = TimeUnit.MILLISECONDS.convert(parseDelayAsLong(fixedDelayString), scheduled.timeUnit());
} }
catch (RuntimeException ex) { catch (RuntimeException ex) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
@ -472,7 +475,7 @@ public class ScheduledAnnotationBeanPostProcessor
} }
// Check fixed rate // Check fixed rate
long fixedRate = scheduled.fixedRate(); long fixedRate = TimeUnit.MILLISECONDS.convert(scheduled.fixedRate(), scheduled.timeUnit());
if (fixedRate >= 0) { if (fixedRate >= 0) {
Assert.isTrue(!processedSchedule, errorMessage); Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true; processedSchedule = true;
@ -487,7 +490,7 @@ public class ScheduledAnnotationBeanPostProcessor
Assert.isTrue(!processedSchedule, errorMessage); Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true; processedSchedule = true;
try { try {
fixedRate = parseDelayAsLong(fixedRateString); fixedRate = TimeUnit.MILLISECONDS.convert(parseDelayAsLong(fixedRateString), scheduled.timeUnit());
} }
catch (RuntimeException ex) { catch (RuntimeException ex) {
throw new IllegalArgumentException( throw new IllegalArgumentException(

216
spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -29,6 +29,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -67,6 +68,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* @author Chris Beams * @author Chris Beams
* @author Sam Brannen * @author Sam Brannen
* @author Stevo Slavić * @author Stevo Slavić
* @author Victor Brown
*/ */
public class ScheduledAnnotationBeanPostProcessorTests { public class ScheduledAnnotationBeanPostProcessorTests {
@ -107,6 +109,62 @@ public class ScheduledAnnotationBeanPostProcessorTests {
assertThat(task.getInterval()).isEqualTo(5000L); assertThat(task.getInterval()).isEqualTo(5000L);
} }
@Test
public void fixedDelayWithSecondsTimeUnitTask() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(FixedDelayWithSecondsTimeUnitTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1);
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@SuppressWarnings("unchecked")
List<IntervalTask> fixedDelayTasks = (List<IntervalTask>)
new DirectFieldAccessor(registrar).getPropertyValue("fixedDelayTasks");
assertThat(fixedDelayTasks.size()).isEqualTo(1);
IntervalTask task = fixedDelayTasks.get(0);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedDelay");
assertThat(task.getInitialDelay()).isEqualTo(0L);
assertThat(task.getInterval()).isEqualTo(5000L);
}
@Test
public void fixedDelayWithMinutesTimeUnitTask() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(FixedDelayWithMinutesTimeUnitTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1);
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@SuppressWarnings("unchecked")
List<IntervalTask> fixedDelayTasks = (List<IntervalTask>)
new DirectFieldAccessor(registrar).getPropertyValue("fixedDelayTasks");
assertThat(fixedDelayTasks.size()).isEqualTo(1);
IntervalTask task = fixedDelayTasks.get(0);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedDelay");
assertThat(task.getInitialDelay()).isEqualTo(0L);
assertThat(task.getInterval()).isEqualTo(180000L);
}
@Test @Test
public void fixedRateTask() { public void fixedRateTask() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
@ -135,6 +193,62 @@ public class ScheduledAnnotationBeanPostProcessorTests {
assertThat(task.getInterval()).isEqualTo(3000L); assertThat(task.getInterval()).isEqualTo(3000L);
} }
@Test
public void fixedRateWithSecondsTimeUnitTask() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(FixedRateWithSecondsTimeUnitTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1);
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");
assertThat(fixedRateTasks.size()).isEqualTo(1);
IntervalTask task = fixedRateTasks.get(0);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedRate");
assertThat(task.getInitialDelay()).isEqualTo(0L);
assertThat(task.getInterval()).isEqualTo(5000L);
}
@Test
public void fixedRateWithMinutesTimeUnitTask() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(FixedRateWithMinutesTimeUnitTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1);
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");
assertThat(fixedRateTasks.size()).isEqualTo(1);
IntervalTask task = fixedRateTasks.get(0);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedRate");
assertThat(task.getInitialDelay()).isEqualTo(0L);
assertThat(task.getInterval()).isEqualTo(180000L);
}
@Test @Test
public void fixedRateTaskWithInitialDelay() { public void fixedRateTaskWithInitialDelay() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
@ -163,6 +277,62 @@ public class ScheduledAnnotationBeanPostProcessorTests {
assertThat(task.getInterval()).isEqualTo(3000L); assertThat(task.getInterval()).isEqualTo(3000L);
} }
@Test
public void fixedRateTaskWithSecondsTimeUnitWithInitialDelay() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(FixedRateWithSecondsTimeUnitInitialDelayTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1);
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");
assertThat(fixedRateTasks.size()).isEqualTo(1);
IntervalTask task = fixedRateTasks.get(0);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedRate");
assertThat(task.getInitialDelay()).isEqualTo(5000L);
assertThat(task.getInterval()).isEqualTo(3000L);
}
@Test
public void fixedRateTaskWithMinutesTimeUnitWithInitialDelay() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(FixedRateWithMinutesTimeUnitInitialDelayTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1);
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");
assertThat(fixedRateTasks.size()).isEqualTo(1);
IntervalTask task = fixedRateTasks.get(0);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedRate");
assertThat(task.getInitialDelay()).isEqualTo(60000L);
assertThat(task.getInterval()).isEqualTo(180000L);
}
@Test @Test
public void severalFixedRatesWithRepeatedScheduledAnnotation() { public void severalFixedRatesWithRepeatedScheduledAnnotation() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
@ -702,6 +872,22 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
} }
static class FixedDelayWithSecondsTimeUnitTestBean {
@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
public void fixedDelay() {
}
}
static class FixedDelayWithMinutesTimeUnitTestBean {
@Scheduled(fixedDelay = 3, timeUnit = TimeUnit.MINUTES)
public void fixedDelay() {
}
}
static class FixedRateTestBean { static class FixedRateTestBean {
@ -709,6 +895,20 @@ public class ScheduledAnnotationBeanPostProcessorTests {
public void fixedRate() { public void fixedRate() {
} }
} }
static class FixedRateWithSecondsTimeUnitTestBean {
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS)
public void fixedRate() {
}
}
static class FixedRateWithMinutesTimeUnitTestBean {
@Scheduled(fixedRate = 3, timeUnit = TimeUnit.MINUTES)
public void fixedRate() {
}
}
static class FixedRateWithInitialDelayTestBean { static class FixedRateWithInitialDelayTestBean {
@ -718,6 +918,20 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
} }
static class FixedRateWithSecondsTimeUnitInitialDelayTestBean {
@Scheduled(fixedRate = 3, initialDelay = 5, timeUnit = TimeUnit.SECONDS)
public void fixedRate() {
}
}
static class FixedRateWithMinutesTimeUnitInitialDelayTestBean {
@Scheduled(fixedRate = 3, initialDelay = 1, timeUnit = TimeUnit.MINUTES)
public void fixedRate() {
}
}
static class SeveralFixedRatesWithSchedulesContainerAnnotationTestBean { static class SeveralFixedRatesWithSchedulesContainerAnnotationTestBean {

Loading…
Cancel
Save