From 3f7d0171bfb9a33e8333cecda2709c60e40243c1 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 de7866073931..ce55aeb82cbd 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 @@ * @author Dave Syer * @author Chris Beams * @author Victor Brown + * @author Sam Brannen * @since 3.0 * @see EnableScheduling * @see ScheduledAnnotationBeanPostProcessor @@ -103,63 +104,74 @@ 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 52fae5dbab98..5e47ccda548f 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 @@ * @author Chris Beams * @author Elizabeth Chatman * @author Victor Brown + * @author Sam Brannen * @since 3.0 * @see Scheduled * @see EnableScheduling @@ -385,7 +386,7 @@ public Object postProcessAfterInitialization(Object bean, String beanName) { /** * 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 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean) 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 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean) } 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 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean) } // 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 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean) 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 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean) } // 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 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean) 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 @@ protected Runnable createRunnable(Object target, Method method) { 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 494bf075c6df..04a08b00e64c 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 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 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 @@ * @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 void fixedDelayWithMinutesTimeUnitTask() { @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 void fixedDelayWithMinutesTimeUnitTask() { 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 void fixedRateTaskWithSecondsTimeUnitWithInitialDelay() { 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 void fixedRateTaskWithMinutesTimeUnitWithInitialDelay() { 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 @@ private void severalFixedRates(StaticApplicationContext context, } @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 void cronTask() { } @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 void cronTaskWithZone() { } @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 void cronTaskWithInvalidZone() { } @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 void cronTaskWithMethodValidation() { } @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 void cronTaskWithScopedProxy() { } @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 void metaAnnotationWithFixedRate() { } @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 void composedAnnotationWithInitialDelayAndFixedRate() { } @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 void metaAnnotationWithCronExpression() { } @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 void propertyPlaceholderWithCron() { } @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 void propertyPlaceholderWithInactiveCron() { 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 @@ private void propertyPlaceholderWithFixedDelay(boolean durationFormat) { 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 @@ private void propertyPlaceholderWithFixedRate(boolean durationFormat) { 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 void expressionWithCron() { } @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 void propertyPlaceholderForMetaAnnotation() { } @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 void nonVoidReturnType() { } @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 void emptyAnnotation() { } @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 void invalidCron() throws Throwable { } @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 void nonEmptyParamList() { } - 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 void fixedRate() { static class SeveralFixedRatesWithSchedulesContainerAnnotationTestBean { @Schedules({@Scheduled(fixedRate = 4000), @Scheduled(fixedRate = 4000, initialDelay = 2000)}) - public void fixedRate() { + void fixedRate() { } } @@ -945,7 +804,7 @@ static class SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean { @Scheduled(fixedRate = 4000) @Scheduled(fixedRate = 4000, initialDelay = 2000) - public void fixedRate() { + void fixedRate() { } static SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean nestedProxy() { @@ -962,7 +821,7 @@ static class FixedRatesBaseBean { @Scheduled(fixedRate = 4000) @Scheduled(fixedRate = 4000, initialDelay = 2000) - public void fixedRate() { + void fixedRate() { } } @@ -1006,7 +865,7 @@ protected void cron() throws IOException { 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 void cron() throws IOException { 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 void cron() throws IOException { static class ProxiedCronTestBeanDependent { - public ProxiedCronTestBeanDependent(ProxiedCronTestBean testBean) { + ProxiedCronTestBeanDependent(ProxiedCronTestBean testBean) { } } @@ -1033,7 +892,7 @@ public ProxiedCronTestBeanDependent(ProxiedCronTestBean testBean) { static class NonVoidReturnTypeTestBean { @Scheduled(cron = "0 0 9-17 * * MON-FRI") - public String cron() { + String cron() { return "oops"; } } @@ -1042,7 +901,7 @@ public String cron() { static class EmptyAnnotationTestBean { @Scheduled - public void invalid() { + void invalid() { } } @@ -1050,7 +909,7 @@ public void invalid() { static class InvalidCronTestBean { @Scheduled(cron = "abc") - public void invalid() { + void invalid() { } } @@ -1058,7 +917,7 @@ public void invalid() { static class NonEmptyParamListTestBean { @Scheduled(fixedRate = 3000) - public void invalid(String oops) { + void invalid(String oops) { } } @@ -1090,7 +949,7 @@ public void invalid(String oops) { static class MetaAnnotationFixedRateTestBean { @EveryFiveSeconds - public void checkForUpdates() { + void checkForUpdates() { } } @@ -1098,7 +957,7 @@ public void checkForUpdates() { static class ComposedAnnotationFixedRateTestBean { @WaitASec(fixedRate = 5000) - public void checkForUpdates() { + void checkForUpdates() { } } @@ -1106,7 +965,7 @@ public void checkForUpdates() { static class MetaAnnotationCronTestBean { @Hourly - public void generateReport() { + void generateReport() { } } @@ -1114,23 +973,37 @@ public void generateReport() { 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 void fixedRate() { static class ExpressionWithCronTestBean { @Scheduled(cron = "#{schedules.businessHours}") - public void x() { + void x() { } } @@ -1153,7 +1026,24 @@ public void x() { 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 15e49e487d23..dae0ac82ac01 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.