From bd72e4498b9d1824646c88fa6fa08c1780484d2f Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 25 Aug 2021 20:46:51 +0200 Subject: [PATCH] Revise and document TimeUnit support in @Scheduled This commit also fixes a bug introduced in commit e99b43b91e, where java.time.Duration strings were converted to milliseconds and then converted again using the configured TimeUnit. See gh-27309 --- .../scheduling/annotation/Scheduled.java | 54 ++- .../ScheduledAnnotationBeanPostProcessor.java | 29 +- ...duledAnnotationBeanPostProcessorTests.java | 454 +++++++----------- src/docs/asciidoc/integration.adoc | 41 +- 4 files changed, 253 insertions(+), 325 deletions(-) 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 de78660739..ce55aeb82c 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 @@ -48,6 +48,7 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar; * @author Dave Syer * @author Chris Beams * @author Victor Brown + * @author Sam Brannen * @since 3.0 * @see EnableScheduling * @see ScheduledAnnotationBeanPostProcessor @@ -103,63 +104,74 @@ public @interface Scheduled { String zone() default ""; /** - * Execute the annotated method with a fixed period between the - * end of the last invocation and the start of the next. - * Using milliseconds by default with timeUnit(). + * Execute the annotated method with a fixed period between the end of the + * last invocation and the start of the next. + *

The time unit is milliseconds by default but can be overridden via + * {@link #timeUnit}. * @return the delay */ long fixedDelay() default -1; /** - * Execute the annotated method with a fixed period between the - * end of the last invocation and the start of the next. - * Using milliseconds by default with fixedDelayTimeUnit(). - * @return the delay as a String value, e.g. a placeholder + * Execute the annotated method with a fixed period between the end of the + * last invocation and the start of the next. + *

The time unit is milliseconds by default but can be overridden via + * {@link #timeUnit}. + * @return the delay as a String value — for example, a placeholder * or a {@link java.time.Duration#parse java.time.Duration} compliant value * @since 3.2.2 */ String fixedDelayString() default ""; /** - * Execute the annotated method with a fixed period between - * invocations. - * Using milliseconds by default with timeUnit(). + * Execute the annotated method with a fixed period between invocations. + *

The time unit is milliseconds by default but can be overridden via + * {@link #timeUnit}. * @return the period */ long fixedRate() default -1; /** - * Execute the annotated method with a fixed period between - * invocations. - * Using milliseconds by default with fixedRateTimeUnit(). - * @return the period as a String value, e.g. a placeholder + * Execute the annotated method with a fixed period between invocations. + *

The time unit is milliseconds by default but can be overridden via + * {@link #timeUnit}. + * @return the period as a String value — for example, a placeholder * or a {@link java.time.Duration#parse java.time.Duration} compliant value * @since 3.2.2 */ String fixedRateString() default ""; /** - * Number to delay before the first execution of a + * Number of units of time to delay before the first execution of a * {@link #fixedRate} or {@link #fixedDelay} task. - * Using milliseconds by default with timeUnit(). + *

The time unit is milliseconds by default but can be overridden via + * {@link #timeUnit}. * @return the initial * @since 3.2 */ long initialDelay() default -1; /** - * Number to delay before the first execution of a + * Number of units of time to delay before the first execution of a * {@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 + *

The time unit is milliseconds by default but can be overridden via + * {@link #timeUnit}. + * @return the initial delay as a String value — for example, a placeholder * or a {@link java.time.Duration#parse java.time.Duration} compliant value * @since 3.2.2 */ 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. + * The {@link TimeUnit} to use for {@link #fixedDelay}, {@link #fixedDelayString}, + * {@link #fixedRate}, {@link #fixedRateString}, {@link #initialDelay}, and + * {@link #initialDelayString}. + *

Defaults to {@link TimeUnit#MICROSECONDS}. + *

This attribute is ignored for {@linkplain #cron() cron expressions} + * and for {@link java.time.Duration} values supplied via {@link #fixedDelayString}, + * {@link #fixedRateString}, or {@link #initialDelayString}. + * @return the {@code TimeUnit} to use + * @since 5.3.10 */ TimeUnit timeUnit() default TimeUnit.MILLISECONDS; 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 52fae5dbab..5e47ccda54 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 @@ -96,6 +96,7 @@ import org.springframework.util.StringValueResolver; * @author Chris Beams * @author Elizabeth Chatman * @author Victor Brown + * @author Sam Brannen * @since 3.0 * @see Scheduled * @see EnableScheduling @@ -385,7 +386,7 @@ public class ScheduledAnnotationBeanPostProcessor /** * Process the given {@code @Scheduled} method declaration on the given bean. - * @param scheduled the @Scheduled annotation + * @param scheduled the {@code @Scheduled} annotation * @param method the method that the annotation has been declared on * @param bean the target bean instance * @see #createRunnable(Object, Method) @@ -400,7 +401,7 @@ public class ScheduledAnnotationBeanPostProcessor Set tasks = new LinkedHashSet<>(4); // Determine initial delay - long initialDelay = TimeUnit.MILLISECONDS.convert(scheduled.initialDelay(), scheduled.timeUnit()); + long initialDelay = convertToMillis(scheduled.initialDelay(), scheduled.timeUnit()); String initialDelayString = scheduled.initialDelayString(); if (StringUtils.hasText(initialDelayString)) { Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both"); @@ -409,7 +410,7 @@ public class ScheduledAnnotationBeanPostProcessor } if (StringUtils.hasLength(initialDelayString)) { try { - initialDelay = TimeUnit.MILLISECONDS.convert(parseDelayAsLong(initialDelayString), scheduled.timeUnit()); + initialDelay = convertToMillis(initialDelayString, scheduled.timeUnit()); } catch (RuntimeException ex) { throw new IllegalArgumentException( @@ -448,7 +449,7 @@ public class ScheduledAnnotationBeanPostProcessor } // Check fixed delay - long fixedDelay = TimeUnit.MILLISECONDS.convert(scheduled.fixedDelay(), scheduled.timeUnit()); + long fixedDelay = convertToMillis(scheduled.fixedDelay(), scheduled.timeUnit()); if (fixedDelay >= 0) { Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; @@ -464,7 +465,7 @@ public class ScheduledAnnotationBeanPostProcessor Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; try { - fixedDelay = TimeUnit.MILLISECONDS.convert(parseDelayAsLong(fixedDelayString), scheduled.timeUnit()); + fixedDelay = convertToMillis(fixedDelayString, scheduled.timeUnit()); } catch (RuntimeException ex) { throw new IllegalArgumentException( @@ -475,7 +476,7 @@ public class ScheduledAnnotationBeanPostProcessor } // Check fixed rate - long fixedRate = TimeUnit.MILLISECONDS.convert(scheduled.fixedRate(), scheduled.timeUnit()); + long fixedRate = convertToMillis(scheduled.fixedRate(), scheduled.timeUnit()); if (fixedRate >= 0) { Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; @@ -490,7 +491,7 @@ public class ScheduledAnnotationBeanPostProcessor Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; try { - fixedRate = TimeUnit.MILLISECONDS.convert(parseDelayAsLong(fixedRateString), scheduled.timeUnit()); + fixedRate = convertToMillis(fixedRateString, scheduled.timeUnit()); } catch (RuntimeException ex) { throw new IllegalArgumentException( @@ -530,11 +531,19 @@ public class ScheduledAnnotationBeanPostProcessor return new ScheduledMethodRunnable(target, invocableMethod); } - private static long parseDelayAsLong(String value) throws RuntimeException { - if (value.length() > 1 && (isP(value.charAt(0)) || isP(value.charAt(1)))) { + private static long convertToMillis(long value, TimeUnit timeUnit) { + return TimeUnit.MILLISECONDS.convert(value, timeUnit); + } + + private static long convertToMillis(String value, TimeUnit timeUnit) { + if (isDurationString(value)) { return Duration.parse(value).toMillis(); } - return Long.parseLong(value); + return convertToMillis(Long.parseLong(value), timeUnit); + } + + private static boolean isDurationString(String value) { + return (value.length() > 1 && (isP(value.charAt(0)) || isP(value.charAt(1)))); } private static boolean isP(char ch) { 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 494bf075c6..04a08b00e6 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 @@ -33,6 +33,12 @@ import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.converter.ArgumentConversionException; +import org.junit.jupiter.params.converter.ArgumentConverter; +import org.junit.jupiter.params.converter.ConvertWith; +import org.junit.jupiter.params.provider.CsvSource; import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.scope.ScopedProxyUtils; @@ -61,8 +67,11 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.SoftAssertions.assertSoftly; /** + * Tests for {@link ScheduledAnnotationBeanPostProcessor}. + * * @author Mark Fisher * @author Juergen Hoeller * @author Chris Beams @@ -70,77 +79,25 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; * @author Stevo Slavić * @author Victor Brown */ -public class ScheduledAnnotationBeanPostProcessorTests { +class ScheduledAnnotationBeanPostProcessorTests { private final StaticApplicationContext context = new StaticApplicationContext(); @AfterEach - public void closeContextAfterTest() { + void closeContextAfterTest() { context.close(); } - - @Test - public void fixedDelayTask() { - BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition(FixedDelayTestBean.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 fixedDelayTasks = (List) - 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 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 fixedDelayTasks = (List) - 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() { + @ParameterizedTest + @CsvSource({ + "FixedDelay, 5000", + "FixedDelayInSeconds, 5000", + "FixedDelayInMinutes, 180000" + }) + void fixedDelayTask(@NameToClass Class beanClass, long expectedInterval) { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition(FixedDelayWithMinutesTimeUnitTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(beanClass); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); @@ -154,7 +111,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { @SuppressWarnings("unchecked") List fixedDelayTasks = (List) new DirectFieldAccessor(registrar).getPropertyValue("fixedDelayTasks"); - assertThat(fixedDelayTasks.size()).isEqualTo(1); + assertThat(fixedDelayTasks).hasSize(1); IntervalTask task = fixedDelayTasks.get(0); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); Object targetObject = runnable.getTarget(); @@ -162,125 +119,18 @@ public class ScheduledAnnotationBeanPostProcessorTests { assertThat(targetObject).isEqualTo(target); assertThat(targetMethod.getName()).isEqualTo("fixedDelay"); assertThat(task.getInitialDelay()).isEqualTo(0L); - assertThat(task.getInterval()).isEqualTo(180000L); - } - - @Test - public void fixedRateTask() { - BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition(FixedRateTestBean.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 fixedRateTasks = (List) - 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(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 fixedRateTasks = (List) - 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 fixedRateTasks = (List) - 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 - public void fixedRateTaskWithInitialDelay() { - BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition(FixedRateWithInitialDelayTestBean.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 fixedRateTasks = (List) - 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(1000L); - assertThat(task.getInterval()).isEqualTo(3000L); + assertThat(task.getInterval()).isEqualTo(expectedInterval); } - @Test - public void fixedRateTaskWithSecondsTimeUnitWithInitialDelay() { + @ParameterizedTest + @CsvSource({ + "FixedRate, 3000", + "FixedRateInSeconds, 5000", + "FixedRateInMinutes, 180000" + }) + void fixedRateTask(@NameToClass Class beanClass, long expectedInterval) { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition(FixedRateWithSecondsTimeUnitInitialDelayTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(beanClass); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); @@ -301,14 +151,21 @@ public class ScheduledAnnotationBeanPostProcessorTests { 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() { + assertSoftly(softly -> { + softly.assertThat(task.getInitialDelay()).as("initial delay").isEqualTo(0); + softly.assertThat(task.getInterval()).as("interval").isEqualTo(expectedInterval); + }); + } + + @ParameterizedTest + @CsvSource({ + "FixedRateWithInitialDelay, 1000, 3000", + "FixedRateWithInitialDelayInSeconds, 5000, 3000", + "FixedRateWithInitialDelayInMinutes, 60000, 180000" + }) + void fixedRateTaskWithInitialDelay(@NameToClass Class beanClass, long expectedInitialDelay, long expectedInterval) { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition(FixedRateWithMinutesTimeUnitInitialDelayTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(beanClass); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); @@ -329,40 +186,42 @@ public class ScheduledAnnotationBeanPostProcessorTests { Method targetMethod = runnable.getMethod(); assertThat(targetObject).isEqualTo(target); assertThat(targetMethod.getName()).isEqualTo("fixedRate"); - assertThat(task.getInitialDelay()).isEqualTo(60000L); - assertThat(task.getInterval()).isEqualTo(180000L); + assertSoftly(softly -> { + softly.assertThat(task.getInitialDelay()).as("initial delay").isEqualTo(expectedInitialDelay); + softly.assertThat(task.getInterval()).as("interval").isEqualTo(expectedInterval); + }); } @Test - public void severalFixedRatesWithRepeatedScheduledAnnotation() { + void severalFixedRatesWithRepeatedScheduledAnnotation() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition targetDefinition = new RootBeanDefinition(SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean.class); severalFixedRates(context, processorDefinition, targetDefinition); } @Test - public void severalFixedRatesWithSchedulesContainerAnnotation() { + void severalFixedRatesWithSchedulesContainerAnnotation() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition targetDefinition = new RootBeanDefinition(SeveralFixedRatesWithSchedulesContainerAnnotationTestBean.class); severalFixedRates(context, processorDefinition, targetDefinition); } @Test - public void severalFixedRatesOnBaseClass() { + void severalFixedRatesOnBaseClass() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition targetDefinition = new RootBeanDefinition(FixedRatesSubBean.class); severalFixedRates(context, processorDefinition, targetDefinition); } @Test - public void severalFixedRatesOnDefaultMethod() { + void severalFixedRatesOnDefaultMethod() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition targetDefinition = new RootBeanDefinition(FixedRatesDefaultBean.class); severalFixedRates(context, processorDefinition, targetDefinition); } @Test - public void severalFixedRatesAgainstNestedCglibProxy() { + void severalFixedRatesAgainstNestedCglibProxy() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition targetDefinition = new RootBeanDefinition(SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean.class); targetDefinition.setFactoryMethodName("nestedProxy"); @@ -405,7 +264,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } @Test - public void cronTask() { + void cronTask() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition targetDefinition = new RootBeanDefinition(CronTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); @@ -432,7 +291,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } @Test - public void cronTaskWithZone() { + void cronTaskWithZone() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition targetDefinition = new RootBeanDefinition(CronWithTimezoneTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); @@ -478,7 +337,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } @Test - public void cronTaskWithInvalidZone() { + void cronTaskWithInvalidZone() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition targetDefinition = new RootBeanDefinition(CronWithInvalidTimezoneTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); @@ -488,7 +347,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } @Test - public void cronTaskWithMethodValidation() { + void cronTaskWithMethodValidation() { BeanDefinition validationDefinition = new RootBeanDefinition(MethodValidationPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition targetDefinition = new RootBeanDefinition(CronTestBean.class); @@ -500,7 +359,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } @Test - public void cronTaskWithScopedProxy() { + void cronTaskWithScopedProxy() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); context.registerBeanDefinition("postProcessor", processorDefinition); new AnnotatedBeanDefinitionReader(context).register(ProxiedCronTestBean.class, ProxiedCronTestBeanDependent.class); @@ -525,7 +384,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } @Test - public void metaAnnotationWithFixedRate() { + void metaAnnotationWithFixedRate() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition targetDefinition = new RootBeanDefinition(MetaAnnotationFixedRateTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); @@ -552,7 +411,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } @Test - public void composedAnnotationWithInitialDelayAndFixedRate() { + void composedAnnotationWithInitialDelayAndFixedRate() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition targetDefinition = new RootBeanDefinition(ComposedAnnotationFixedRateTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); @@ -580,7 +439,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } @Test - public void metaAnnotationWithCronExpression() { + void metaAnnotationWithCronExpression() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition targetDefinition = new RootBeanDefinition(MetaAnnotationCronTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); @@ -607,7 +466,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } @Test - public void propertyPlaceholderWithCron() { + void propertyPlaceholderWithCron() { String businessHoursCronExpression = "0 0 9-17 * * MON-FRI"; BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertySourcesPlaceholderConfigurer.class); @@ -640,7 +499,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } @Test - public void propertyPlaceholderWithInactiveCron() { + void propertyPlaceholderWithInactiveCron() { String businessHoursCronExpression = "-"; BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertySourcesPlaceholderConfigurer.class); @@ -657,24 +516,23 @@ public class ScheduledAnnotationBeanPostProcessorTests { assertThat(postProcessor.getScheduledTasks().isEmpty()).isTrue(); } - @Test - public void propertyPlaceholderWithFixedDelayInMillis() { - propertyPlaceholderWithFixedDelay(false); - } - - @Test - public void propertyPlaceholderWithFixedDelayInDuration() { - propertyPlaceholderWithFixedDelay(true); - } + @ParameterizedTest + @CsvSource({ + "PropertyPlaceholderWithFixedDelay, 5000, 1000, 5000, 1000", + "PropertyPlaceholderWithFixedDelay, PT5S, PT1S, 5000, 1000", + "PropertyPlaceholderWithFixedDelayInSeconds, 5000, 1000, 5000000, 1000000", + "PropertyPlaceholderWithFixedDelayInSeconds, PT5S, PT1S, 5000, 1000" + }) + void propertyPlaceholderWithFixedDelay(@NameToClass Class beanClass, String fixedDelay, String initialDelay, + long expectedInterval, long expectedInitialDelay) { - private void propertyPlaceholderWithFixedDelay(boolean durationFormat) { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertySourcesPlaceholderConfigurer.class); Properties properties = new Properties(); - properties.setProperty("fixedDelay", (durationFormat ? "PT5S" : "5000")); - properties.setProperty("initialDelay", (durationFormat ? "PT1S" : "1000")); + properties.setProperty("fixedDelay", fixedDelay); + properties.setProperty("initialDelay", initialDelay); placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties); - BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithFixedDelayTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(beanClass); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("placeholder", placeholderDefinition); context.registerBeanDefinition("target", targetDefinition); @@ -696,28 +554,29 @@ public class ScheduledAnnotationBeanPostProcessorTests { Method targetMethod = runnable.getMethod(); assertThat(targetObject).isEqualTo(target); assertThat(targetMethod.getName()).isEqualTo("fixedDelay"); - assertThat(task.getInitialDelay()).isEqualTo(1000L); - assertThat(task.getInterval()).isEqualTo(5000L); - } - - @Test - public void propertyPlaceholderWithFixedRateInMillis() { - propertyPlaceholderWithFixedRate(false); - } - - @Test - public void propertyPlaceholderWithFixedRateInDuration() { - propertyPlaceholderWithFixedRate(true); - } + assertSoftly(softly -> { + softly.assertThat(task.getInitialDelay()).as("initial delay").isEqualTo(expectedInitialDelay); + softly.assertThat(task.getInterval()).as("interval").isEqualTo(expectedInterval); + }); + } + + @ParameterizedTest + @CsvSource({ + "PropertyPlaceholderWithFixedRate, 3000, 1000, 3000, 1000", + "PropertyPlaceholderWithFixedRate, PT3S, PT1S, 3000, 1000", + "PropertyPlaceholderWithFixedRateInSeconds, 3000, 1000, 3000000, 1000000", + "PropertyPlaceholderWithFixedRateInSeconds, PT3S, PT1S, 3000, 1000" + }) + void propertyPlaceholderWithFixedRate(@NameToClass Class beanClass, String fixedRate, String initialDelay, + long expectedInterval, long expectedInitialDelay) { - private void propertyPlaceholderWithFixedRate(boolean durationFormat) { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertySourcesPlaceholderConfigurer.class); Properties properties = new Properties(); - properties.setProperty("fixedRate", (durationFormat ? "PT3S" : "3000")); - properties.setProperty("initialDelay", (durationFormat ? "PT1S" : "1000")); + properties.setProperty("fixedRate", fixedRate); + properties.setProperty("initialDelay", initialDelay); placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties); - BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithFixedRateTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(beanClass); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("placeholder", placeholderDefinition); context.registerBeanDefinition("target", targetDefinition); @@ -739,12 +598,14 @@ public class ScheduledAnnotationBeanPostProcessorTests { Method targetMethod = runnable.getMethod(); assertThat(targetObject).isEqualTo(target); assertThat(targetMethod.getName()).isEqualTo("fixedRate"); - assertThat(task.getInitialDelay()).isEqualTo(1000L); - assertThat(task.getInterval()).isEqualTo(3000L); + assertSoftly(softly -> { + softly.assertThat(task.getInitialDelay()).as("initial delay").isEqualTo(expectedInitialDelay); + softly.assertThat(task.getInterval()).as("interval").isEqualTo(expectedInterval); + }); } @Test - public void expressionWithCron() { + void expressionWithCron() { String businessHoursCronExpression = "0 0 9-17 * * MON-FRI"; BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition targetDefinition = new RootBeanDefinition(ExpressionWithCronTestBean.class); @@ -775,7 +636,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } @Test - public void propertyPlaceholderForMetaAnnotation() { + void propertyPlaceholderForMetaAnnotation() { String businessHoursCronExpression = "0 0 9-17 * * MON-FRI"; BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertySourcesPlaceholderConfigurer.class); @@ -808,7 +669,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } @Test - public void nonVoidReturnType() { + void nonVoidReturnType() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition targetDefinition = new RootBeanDefinition(NonVoidReturnTypeTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); @@ -835,7 +696,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } @Test - public void emptyAnnotation() { + void emptyAnnotation() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition targetDefinition = new RootBeanDefinition(EmptyAnnotationTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); @@ -845,7 +706,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } @Test - public void invalidCron() throws Throwable { + void invalidCron() throws Throwable { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition targetDefinition = new RootBeanDefinition(InvalidCronTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); @@ -855,7 +716,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } @Test - public void nonEmptyParamList() { + void nonEmptyParamList() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition targetDefinition = new RootBeanDefinition(NonEmptyParamListTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); @@ -865,70 +726,68 @@ public class ScheduledAnnotationBeanPostProcessorTests { } - static class FixedDelayTestBean { + static class FixedDelay { @Scheduled(fixedDelay = 5000) - public void fixedDelay() { + void fixedDelay() { } } - static class FixedDelayWithSecondsTimeUnitTestBean { + static class FixedDelayInSeconds { @Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS) - public void fixedDelay() { + void fixedDelay() { } - } - static class FixedDelayWithMinutesTimeUnitTestBean { + static class FixedDelayInMinutes { @Scheduled(fixedDelay = 3, timeUnit = TimeUnit.MINUTES) - public void fixedDelay() { + void fixedDelay() { } - } - static class FixedRateTestBean { + static class FixedRate { @Scheduled(fixedRate = 3000) - public void fixedRate() { + void fixedRate() { } } - static class FixedRateWithSecondsTimeUnitTestBean { + + static class FixedRateInSeconds { @Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS) - public void fixedRate() { + void fixedRate() { } } - - static class FixedRateWithMinutesTimeUnitTestBean { + static class FixedRateInMinutes { @Scheduled(fixedRate = 3, timeUnit = TimeUnit.MINUTES) - public void fixedRate() { + void fixedRate() { } } - static class FixedRateWithInitialDelayTestBean { + static class FixedRateWithInitialDelay { @Scheduled(fixedRate = 3000, initialDelay = 1000) - public void fixedRate() { + void fixedRate() { } } - static class FixedRateWithSecondsTimeUnitInitialDelayTestBean { + static class FixedRateWithInitialDelayInSeconds { @Scheduled(fixedRate = 3, initialDelay = 5, timeUnit = TimeUnit.SECONDS) - public void fixedRate() { + void fixedRate() { } } - static class FixedRateWithMinutesTimeUnitInitialDelayTestBean { + static class FixedRateWithInitialDelayInMinutes { @Scheduled(fixedRate = 3, initialDelay = 1, timeUnit = TimeUnit.MINUTES) - public void fixedRate() { + void fixedRate() { } } @@ -936,7 +795,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { static class SeveralFixedRatesWithSchedulesContainerAnnotationTestBean { @Schedules({@Scheduled(fixedRate = 4000), @Scheduled(fixedRate = 4000, initialDelay = 2000)}) - public void fixedRate() { + void fixedRate() { } } @@ -945,7 +804,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { @Scheduled(fixedRate = 4000) @Scheduled(fixedRate = 4000, initialDelay = 2000) - public void fixedRate() { + void fixedRate() { } static SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean nestedProxy() { @@ -962,7 +821,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { @Scheduled(fixedRate = 4000) @Scheduled(fixedRate = 4000, initialDelay = 2000) - public void fixedRate() { + void fixedRate() { } } @@ -1006,7 +865,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { static class CronWithInvalidTimezoneTestBean { @Scheduled(cron = "0 0 0-4,6-23 * * ?", zone = "FOO") - public void cron() throws IOException { + void cron() throws IOException { throw new IOException("no no no"); } } @@ -1017,7 +876,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { static class ProxiedCronTestBean { @Scheduled(cron = "*/7 * * * * ?") - public void cron() throws IOException { + void cron() throws IOException { throw new IOException("no no no"); } } @@ -1025,7 +884,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { static class ProxiedCronTestBeanDependent { - public ProxiedCronTestBeanDependent(ProxiedCronTestBean testBean) { + ProxiedCronTestBeanDependent(ProxiedCronTestBean testBean) { } } @@ -1033,7 +892,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { static class NonVoidReturnTypeTestBean { @Scheduled(cron = "0 0 9-17 * * MON-FRI") - public String cron() { + String cron() { return "oops"; } } @@ -1042,7 +901,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { static class EmptyAnnotationTestBean { @Scheduled - public void invalid() { + void invalid() { } } @@ -1050,7 +909,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { static class InvalidCronTestBean { @Scheduled(cron = "abc") - public void invalid() { + void invalid() { } } @@ -1058,7 +917,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { static class NonEmptyParamListTestBean { @Scheduled(fixedRate = 3000) - public void invalid(String oops) { + void invalid(String oops) { } } @@ -1090,7 +949,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { static class MetaAnnotationFixedRateTestBean { @EveryFiveSeconds - public void checkForUpdates() { + void checkForUpdates() { } } @@ -1098,7 +957,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { static class ComposedAnnotationFixedRateTestBean { @WaitASec(fixedRate = 5000) - public void checkForUpdates() { + void checkForUpdates() { } } @@ -1106,7 +965,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { static class MetaAnnotationCronTestBean { @Hourly - public void generateReport() { + void generateReport() { } } @@ -1114,23 +973,37 @@ public class ScheduledAnnotationBeanPostProcessorTests { static class PropertyPlaceholderWithCronTestBean { @Scheduled(cron = "${schedules.businessHours}") - public void x() { + void x() { } } - static class PropertyPlaceholderWithFixedDelayTestBean { + static class PropertyPlaceholderWithFixedDelay { @Scheduled(fixedDelayString = "${fixedDelay}", initialDelayString = "${initialDelay}") - public void fixedDelay() { + void fixedDelay() { } } + static class PropertyPlaceholderWithFixedDelayInSeconds { + + @Scheduled(fixedDelayString = "${fixedDelay}", initialDelayString = "${initialDelay}", timeUnit = TimeUnit.SECONDS) + void fixedDelay() { + } + } - static class PropertyPlaceholderWithFixedRateTestBean { + + static class PropertyPlaceholderWithFixedRate { @Scheduled(fixedRateString = "${fixedRate}", initialDelayString = "${initialDelay}") - public void fixedRate() { + void fixedRate() { + } + } + + static class PropertyPlaceholderWithFixedRateInSeconds { + + @Scheduled(fixedRateString = "${fixedRate}", initialDelayString = "${initialDelay}", timeUnit = TimeUnit.SECONDS) + void fixedRate() { } } @@ -1138,7 +1011,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { static class ExpressionWithCronTestBean { @Scheduled(cron = "#{schedules.businessHours}") - public void x() { + void x() { } } @@ -1153,7 +1026,24 @@ public class ScheduledAnnotationBeanPostProcessorTests { static class PropertyPlaceholderMetaAnnotationTestBean { @BusinessHours - public void y() { + void y() { + } + } + + @Retention(RetentionPolicy.RUNTIME) + @ConvertWith(NameToClass.Converter.class) + private @interface NameToClass { + class Converter implements ArgumentConverter { + @Override + public Class convert(Object beanClassName, ParameterContext context) throws ArgumentConversionException { + try { + String name = getClass().getEnclosingClass().getEnclosingClass().getName() + "$" + beanClassName; + return getClass().getClassLoader().loadClass(name); + } + catch (Exception ex) { + throw new ArgumentConversionException("Failed to convert class name to Class", ex); + } + } } } diff --git a/src/docs/asciidoc/integration.adoc b/src/docs/asciidoc/integration.adoc index 15e49e487d..dae0ac82ac 100644 --- a/src/docs/asciidoc/integration.adoc +++ b/src/docs/asciidoc/integration.adoc @@ -4923,37 +4923,54 @@ switching to `aspectj` mode in combination with compile-time or load-time weavin ==== The `@Scheduled` annotation You can add the `@Scheduled` annotation to a method, along with trigger metadata. For -example, the following method is invoked every five seconds with a fixed delay, -meaning that the period is measured from the completion time of each preceding -invocation: +example, the following method is invoked every five seconds (5000 milliseconds) with a +fixed delay, meaning that the period is measured from the completion time of each +preceding invocation. [source,java,indent=0,subs="verbatim,quotes"] ---- - @Scheduled(fixedDelay=5000) + @Scheduled(fixedDelay = 5000) public void doSomething() { // something that should run periodically } ---- -If you need a fixed-rate execution, you can change the property name specified within -the annotation. The following method is invoked every five seconds (measured between the -successive start times of each invocation): +[NOTE] +==== +By default, milliseconds will be used as the time unit for fixed delay, fixed rate, and +initial delay values. If you would like to use a different time unit such as seconds or +minutes, you can configure this via the `timeUnit` attribute in `@Scheduled`. + +For example, the previous example can also be written as follows. + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS) + public void doSomething() { + // something that should run periodically + } +---- +==== + +If you need a fixed-rate execution, you can use the `fixedRate` attribute within the +annotation. The following method is invoked every five seconds (measured between the +successive start times of each invocation). [source,java,indent=0,subs="verbatim,quotes"] ---- - @Scheduled(fixedRate=5000) + @Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS) public void doSomething() { // something that should run periodically } ---- For fixed-delay and fixed-rate tasks, you can specify an initial delay by indicating the -number of milliseconds to wait before the first execution of the method, as the following -`fixedRate` example shows: +amount of time to wait before the first execution of the method, as the following +`fixedRate` example shows. [source,java,indent=0,subs="verbatim,quotes"] ---- - @Scheduled(initialDelay=1000, fixedRate=5000) + @Scheduled(initialDelay = 1000, fixedRate = 5000) public void doSomething() { // something that should run periodically } @@ -4975,7 +4992,7 @@ The following example runs only on weekdays: TIP: You can also use the `zone` attribute to specify the time zone in which the cron expression is resolved. -Notice that the methods to be scheduled must have void returns and must not expect any +Notice that the methods to be scheduled must have void returns and must not accept any arguments. If the method needs to interact with other objects from the application context, those would typically have been provided through dependency injection.