Browse Source

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
pull/27330/head
Sam Brannen 3 years ago
parent
commit
bd72e4498b
  1. 54
      spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
  2. 29
      spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
  3. 454
      spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java
  4. 41
      src/docs/asciidoc/integration.adoc

54
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 Dave Syer
* @author Chris Beams * @author Chris Beams
* @author Victor Brown * @author Victor Brown
* @author Sam Brannen
* @since 3.0 * @since 3.0
* @see EnableScheduling * @see EnableScheduling
* @see ScheduledAnnotationBeanPostProcessor * @see ScheduledAnnotationBeanPostProcessor
@ -103,63 +104,74 @@ public @interface Scheduled {
String zone() default ""; String zone() default "";
/** /**
* Execute the annotated method with a fixed period between the * Execute the annotated method with a fixed period between the end of the
* end of the last invocation and the start of the next. * last invocation and the start of the next.
* Using milliseconds by default with timeUnit(). * <p>The time unit is milliseconds by default but can be overridden via
* {@link #timeUnit}.
* @return the delay * @return the delay
*/ */
long fixedDelay() default -1; long fixedDelay() default -1;
/** /**
* Execute the annotated method with a fixed period between the * Execute the annotated method with a fixed period between the end of the
* end of the last invocation and the start of the next. * last invocation and the start of the next.
* Using milliseconds by default with fixedDelayTimeUnit(). * <p>The time unit is milliseconds by default but can be overridden via
* @return the delay as a String value, e.g. a placeholder * {@link #timeUnit}.
* @return the delay as a String value &mdash; for example, a placeholder
* or a {@link java.time.Duration#parse java.time.Duration} compliant value * or a {@link java.time.Duration#parse java.time.Duration} compliant value
* @since 3.2.2 * @since 3.2.2
*/ */
String fixedDelayString() default ""; String fixedDelayString() default "";
/** /**
* Execute the annotated method with a fixed period between * Execute the annotated method with a fixed period between invocations.
* invocations. * <p>The time unit is milliseconds by default but can be overridden via
* Using milliseconds by default with timeUnit(). * {@link #timeUnit}.
* @return the period * @return the period
*/ */
long fixedRate() default -1; long fixedRate() default -1;
/** /**
* Execute the annotated method with a fixed period between * Execute the annotated method with a fixed period between invocations.
* invocations. * <p>The time unit is milliseconds by default but can be overridden via
* Using milliseconds by default with fixedRateTimeUnit(). * {@link #timeUnit}.
* @return the period as a String value, e.g. a placeholder * @return the period as a String value &mdash; for example, a placeholder
* or a {@link java.time.Duration#parse java.time.Duration} compliant value * or a {@link java.time.Duration#parse java.time.Duration} compliant value
* @since 3.2.2 * @since 3.2.2
*/ */
String fixedRateString() default ""; String fixedRateString() default "";
/** /**
* Number 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. * {@link #fixedRate} or {@link #fixedDelay} task.
* Using milliseconds by default with timeUnit(). * <p>The time unit is milliseconds by default but can be overridden via
* {@link #timeUnit}.
* @return the initial * @return the initial
* @since 3.2 * @since 3.2
*/ */
long initialDelay() default -1; 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. * {@link #fixedRate} or {@link #fixedDelay} task.
* Using milliseconds by default with initialDelayTimeUnit(). * <p>The time unit is milliseconds by default but can be overridden via
* @return the initial delay in milliseconds as a String value, e.g. a placeholder * {@link #timeUnit}.
* @return the initial delay as a String value &mdash; for example, a placeholder
* or a {@link java.time.Duration#parse java.time.Duration} compliant value * or a {@link java.time.Duration#parse java.time.Duration} compliant value
* @since 3.2.2 * @since 3.2.2
*/ */
String initialDelayString() default ""; String initialDelayString() default "";
/** /**
* Specify the {@link TimeUnit} to use for initialDelay, fixedRate and fixedDelay values. * The {@link TimeUnit} to use for {@link #fixedDelay}, {@link #fixedDelayString},
* @return the {@link TimeUnit}, by default milliseconds will be used. * {@link #fixedRate}, {@link #fixedRateString}, {@link #initialDelay}, and
* {@link #initialDelayString}.
* <p>Defaults to {@link TimeUnit#MICROSECONDS}.
* <p>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; TimeUnit timeUnit() default TimeUnit.MILLISECONDS;

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

@ -96,6 +96,7 @@ import org.springframework.util.StringValueResolver;
* @author Chris Beams * @author Chris Beams
* @author Elizabeth Chatman * @author Elizabeth Chatman
* @author Victor Brown * @author Victor Brown
* @author Sam Brannen
* @since 3.0 * @since 3.0
* @see Scheduled * @see Scheduled
* @see EnableScheduling * @see EnableScheduling
@ -385,7 +386,7 @@ public class ScheduledAnnotationBeanPostProcessor
/** /**
* Process the given {@code @Scheduled} method declaration on the given bean. * 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 method the method that the annotation has been declared on
* @param bean the target bean instance * @param bean the target bean instance
* @see #createRunnable(Object, Method) * @see #createRunnable(Object, Method)
@ -400,7 +401,7 @@ public class ScheduledAnnotationBeanPostProcessor
Set<ScheduledTask> tasks = new LinkedHashSet<>(4); Set<ScheduledTask> tasks = new LinkedHashSet<>(4);
// Determine initial delay // Determine initial delay
long initialDelay = TimeUnit.MILLISECONDS.convert(scheduled.initialDelay(), scheduled.timeUnit()); long initialDelay = convertToMillis(scheduled.initialDelay(), scheduled.timeUnit());
String initialDelayString = scheduled.initialDelayString(); String initialDelayString = scheduled.initialDelayString();
if (StringUtils.hasText(initialDelayString)) { if (StringUtils.hasText(initialDelayString)) {
Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both"); Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
@ -409,7 +410,7 @@ public class ScheduledAnnotationBeanPostProcessor
} }
if (StringUtils.hasLength(initialDelayString)) { if (StringUtils.hasLength(initialDelayString)) {
try { try {
initialDelay = TimeUnit.MILLISECONDS.convert(parseDelayAsLong(initialDelayString), scheduled.timeUnit()); initialDelay = convertToMillis(initialDelayString, scheduled.timeUnit());
} }
catch (RuntimeException ex) { catch (RuntimeException ex) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
@ -448,7 +449,7 @@ public class ScheduledAnnotationBeanPostProcessor
} }
// Check fixed delay // Check fixed delay
long fixedDelay = TimeUnit.MILLISECONDS.convert(scheduled.fixedDelay(), scheduled.timeUnit()); long fixedDelay = convertToMillis(scheduled.fixedDelay(), scheduled.timeUnit());
if (fixedDelay >= 0) { if (fixedDelay >= 0) {
Assert.isTrue(!processedSchedule, errorMessage); Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true; processedSchedule = true;
@ -464,7 +465,7 @@ public class ScheduledAnnotationBeanPostProcessor
Assert.isTrue(!processedSchedule, errorMessage); Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true; processedSchedule = true;
try { try {
fixedDelay = TimeUnit.MILLISECONDS.convert(parseDelayAsLong(fixedDelayString), scheduled.timeUnit()); fixedDelay = convertToMillis(fixedDelayString, scheduled.timeUnit());
} }
catch (RuntimeException ex) { catch (RuntimeException ex) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
@ -475,7 +476,7 @@ public class ScheduledAnnotationBeanPostProcessor
} }
// Check fixed rate // Check fixed rate
long fixedRate = TimeUnit.MILLISECONDS.convert(scheduled.fixedRate(), scheduled.timeUnit()); long fixedRate = convertToMillis(scheduled.fixedRate(), scheduled.timeUnit());
if (fixedRate >= 0) { if (fixedRate >= 0) {
Assert.isTrue(!processedSchedule, errorMessage); Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true; processedSchedule = true;
@ -490,7 +491,7 @@ public class ScheduledAnnotationBeanPostProcessor
Assert.isTrue(!processedSchedule, errorMessage); Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true; processedSchedule = true;
try { try {
fixedRate = TimeUnit.MILLISECONDS.convert(parseDelayAsLong(fixedRateString), scheduled.timeUnit()); fixedRate = convertToMillis(fixedRateString, scheduled.timeUnit());
} }
catch (RuntimeException ex) { catch (RuntimeException ex) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
@ -530,11 +531,19 @@ public class ScheduledAnnotationBeanPostProcessor
return new ScheduledMethodRunnable(target, invocableMethod); return new ScheduledMethodRunnable(target, invocableMethod);
} }
private static long parseDelayAsLong(String value) throws RuntimeException { private static long convertToMillis(long value, TimeUnit timeUnit) {
if (value.length() > 1 && (isP(value.charAt(0)) || isP(value.charAt(1)))) { return TimeUnit.MILLISECONDS.convert(value, timeUnit);
}
private static long convertToMillis(String value, TimeUnit timeUnit) {
if (isDurationString(value)) {
return Duration.parse(value).toMillis(); 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) { private static boolean isP(char ch) {

454
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.AfterEach;
import org.junit.jupiter.api.Test; 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.framework.ProxyFactory;
import org.springframework.aop.scope.ScopedProxyUtils; 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.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
/** /**
* Tests for {@link ScheduledAnnotationBeanPostProcessor}.
*
* @author Mark Fisher * @author Mark Fisher
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Chris Beams * @author Chris Beams
@ -70,77 +79,25 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* @author Stevo Slavić * @author Stevo Slavić
* @author Victor Brown * @author Victor Brown
*/ */
public class ScheduledAnnotationBeanPostProcessorTests { class ScheduledAnnotationBeanPostProcessorTests {
private final StaticApplicationContext context = new StaticApplicationContext(); private final StaticApplicationContext context = new StaticApplicationContext();
@AfterEach @AfterEach
public void closeContextAfterTest() { void closeContextAfterTest() {
context.close(); context.close();
} }
@ParameterizedTest
@Test @CsvSource({
public void fixedDelayTask() { "FixedDelay, 5000",
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); "FixedDelayInSeconds, 5000",
BeanDefinition targetDefinition = new RootBeanDefinition(FixedDelayTestBean.class); "FixedDelayInMinutes, 180000"
context.registerBeanDefinition("postProcessor", processorDefinition); })
context.registerBeanDefinition("target", targetDefinition); void fixedDelayTask(@NameToClass Class<?> beanClass, long expectedInterval) {
context.refresh();
ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1);
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@SuppressWarnings("unchecked")
List<IntervalTask> fixedDelayTasks = (List<IntervalTask>)
new DirectFieldAccessor(registrar).getPropertyValue("fixedDelayTasks");
assertThat(fixedDelayTasks.size()).isEqualTo(1);
IntervalTask task = fixedDelayTasks.get(0);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedDelay");
assertThat(task.getInitialDelay()).isEqualTo(0L);
assertThat(task.getInterval()).isEqualTo(5000L);
}
@Test
public void fixedDelayWithSecondsTimeUnitTask() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(FixedDelayWithSecondsTimeUnitTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1);
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@SuppressWarnings("unchecked")
List<IntervalTask> fixedDelayTasks = (List<IntervalTask>)
new DirectFieldAccessor(registrar).getPropertyValue("fixedDelayTasks");
assertThat(fixedDelayTasks.size()).isEqualTo(1);
IntervalTask task = fixedDelayTasks.get(0);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedDelay");
assertThat(task.getInitialDelay()).isEqualTo(0L);
assertThat(task.getInterval()).isEqualTo(5000L);
}
@Test
public void fixedDelayWithMinutesTimeUnitTask() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(FixedDelayWithMinutesTimeUnitTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(beanClass);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
@ -154,7 +111,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<IntervalTask> fixedDelayTasks = (List<IntervalTask>) List<IntervalTask> fixedDelayTasks = (List<IntervalTask>)
new DirectFieldAccessor(registrar).getPropertyValue("fixedDelayTasks"); new DirectFieldAccessor(registrar).getPropertyValue("fixedDelayTasks");
assertThat(fixedDelayTasks.size()).isEqualTo(1); assertThat(fixedDelayTasks).hasSize(1);
IntervalTask task = fixedDelayTasks.get(0); IntervalTask task = fixedDelayTasks.get(0);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget(); Object targetObject = runnable.getTarget();
@ -162,125 +119,18 @@ public class ScheduledAnnotationBeanPostProcessorTests {
assertThat(targetObject).isEqualTo(target); assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedDelay"); assertThat(targetMethod.getName()).isEqualTo("fixedDelay");
assertThat(task.getInitialDelay()).isEqualTo(0L); assertThat(task.getInitialDelay()).isEqualTo(0L);
assertThat(task.getInterval()).isEqualTo(180000L); assertThat(task.getInterval()).isEqualTo(expectedInterval);
}
@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<IntervalTask> fixedRateTasks = (List<IntervalTask>)
new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks");
assertThat(fixedRateTasks.size()).isEqualTo(1);
IntervalTask task = fixedRateTasks.get(0);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedRate");
assertThat(task.getInitialDelay()).isEqualTo(0L);
assertThat(task.getInterval()).isEqualTo(3000L);
}
@Test
public void fixedRateWithSecondsTimeUnitTask() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(FixedRateWithSecondsTimeUnitTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1);
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@SuppressWarnings("unchecked")
List<IntervalTask> fixedRateTasks = (List<IntervalTask>)
new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks");
assertThat(fixedRateTasks.size()).isEqualTo(1);
IntervalTask task = fixedRateTasks.get(0);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedRate");
assertThat(task.getInitialDelay()).isEqualTo(0L);
assertThat(task.getInterval()).isEqualTo(5000L);
}
@Test
public void fixedRateWithMinutesTimeUnitTask() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(FixedRateWithMinutesTimeUnitTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1);
Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@SuppressWarnings("unchecked")
List<IntervalTask> fixedRateTasks = (List<IntervalTask>)
new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks");
assertThat(fixedRateTasks.size()).isEqualTo(1);
IntervalTask task = fixedRateTasks.get(0);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedRate");
assertThat(task.getInitialDelay()).isEqualTo(0L);
assertThat(task.getInterval()).isEqualTo(180000L);
}
@Test
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<IntervalTask> fixedRateTasks = (List<IntervalTask>)
new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks");
assertThat(fixedRateTasks.size()).isEqualTo(1);
IntervalTask task = fixedRateTasks.get(0);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedRate");
assertThat(task.getInitialDelay()).isEqualTo(1000L);
assertThat(task.getInterval()).isEqualTo(3000L);
} }
@Test @ParameterizedTest
public void fixedRateTaskWithSecondsTimeUnitWithInitialDelay() { @CsvSource({
"FixedRate, 3000",
"FixedRateInSeconds, 5000",
"FixedRateInMinutes, 180000"
})
void fixedRateTask(@NameToClass Class<?> beanClass, long expectedInterval) {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(FixedRateWithSecondsTimeUnitInitialDelayTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(beanClass);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
@ -301,14 +151,21 @@ public class ScheduledAnnotationBeanPostProcessorTests {
Method targetMethod = runnable.getMethod(); Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target); assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedRate"); assertThat(targetMethod.getName()).isEqualTo("fixedRate");
assertThat(task.getInitialDelay()).isEqualTo(5000L); assertSoftly(softly -> {
assertThat(task.getInterval()).isEqualTo(3000L); softly.assertThat(task.getInitialDelay()).as("initial delay").isEqualTo(0);
} softly.assertThat(task.getInterval()).as("interval").isEqualTo(expectedInterval);
});
@Test }
public void fixedRateTaskWithMinutesTimeUnitWithInitialDelay() {
@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 processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(FixedRateWithMinutesTimeUnitInitialDelayTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(beanClass);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
@ -329,40 +186,42 @@ public class ScheduledAnnotationBeanPostProcessorTests {
Method targetMethod = runnable.getMethod(); Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target); assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedRate"); assertThat(targetMethod.getName()).isEqualTo("fixedRate");
assertThat(task.getInitialDelay()).isEqualTo(60000L); assertSoftly(softly -> {
assertThat(task.getInterval()).isEqualTo(180000L); softly.assertThat(task.getInitialDelay()).as("initial delay").isEqualTo(expectedInitialDelay);
softly.assertThat(task.getInterval()).as("interval").isEqualTo(expectedInterval);
});
} }
@Test @Test
public void severalFixedRatesWithRepeatedScheduledAnnotation() { void severalFixedRatesWithRepeatedScheduledAnnotation() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean.class);
severalFixedRates(context, processorDefinition, targetDefinition); severalFixedRates(context, processorDefinition, targetDefinition);
} }
@Test @Test
public void severalFixedRatesWithSchedulesContainerAnnotation() { void severalFixedRatesWithSchedulesContainerAnnotation() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(SeveralFixedRatesWithSchedulesContainerAnnotationTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(SeveralFixedRatesWithSchedulesContainerAnnotationTestBean.class);
severalFixedRates(context, processorDefinition, targetDefinition); severalFixedRates(context, processorDefinition, targetDefinition);
} }
@Test @Test
public void severalFixedRatesOnBaseClass() { void severalFixedRatesOnBaseClass() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(FixedRatesSubBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(FixedRatesSubBean.class);
severalFixedRates(context, processorDefinition, targetDefinition); severalFixedRates(context, processorDefinition, targetDefinition);
} }
@Test @Test
public void severalFixedRatesOnDefaultMethod() { void severalFixedRatesOnDefaultMethod() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(FixedRatesDefaultBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(FixedRatesDefaultBean.class);
severalFixedRates(context, processorDefinition, targetDefinition); severalFixedRates(context, processorDefinition, targetDefinition);
} }
@Test @Test
public void severalFixedRatesAgainstNestedCglibProxy() { void severalFixedRatesAgainstNestedCglibProxy() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean.class);
targetDefinition.setFactoryMethodName("nestedProxy"); targetDefinition.setFactoryMethodName("nestedProxy");
@ -405,7 +264,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Test @Test
public void cronTask() { void cronTask() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(CronTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(CronTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
@ -432,7 +291,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Test @Test
public void cronTaskWithZone() { void cronTaskWithZone() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(CronWithTimezoneTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(CronWithTimezoneTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
@ -478,7 +337,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Test @Test
public void cronTaskWithInvalidZone() { void cronTaskWithInvalidZone() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(CronWithInvalidTimezoneTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(CronWithInvalidTimezoneTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
@ -488,7 +347,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Test @Test
public void cronTaskWithMethodValidation() { void cronTaskWithMethodValidation() {
BeanDefinition validationDefinition = new RootBeanDefinition(MethodValidationPostProcessor.class); BeanDefinition validationDefinition = new RootBeanDefinition(MethodValidationPostProcessor.class);
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(CronTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(CronTestBean.class);
@ -500,7 +359,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Test @Test
public void cronTaskWithScopedProxy() { void cronTaskWithScopedProxy() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
new AnnotatedBeanDefinitionReader(context).register(ProxiedCronTestBean.class, ProxiedCronTestBeanDependent.class); new AnnotatedBeanDefinitionReader(context).register(ProxiedCronTestBean.class, ProxiedCronTestBeanDependent.class);
@ -525,7 +384,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Test @Test
public void metaAnnotationWithFixedRate() { void metaAnnotationWithFixedRate() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(MetaAnnotationFixedRateTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(MetaAnnotationFixedRateTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
@ -552,7 +411,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Test @Test
public void composedAnnotationWithInitialDelayAndFixedRate() { void composedAnnotationWithInitialDelayAndFixedRate() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(ComposedAnnotationFixedRateTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(ComposedAnnotationFixedRateTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
@ -580,7 +439,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Test @Test
public void metaAnnotationWithCronExpression() { void metaAnnotationWithCronExpression() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(MetaAnnotationCronTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(MetaAnnotationCronTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
@ -607,7 +466,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Test @Test
public void propertyPlaceholderWithCron() { void propertyPlaceholderWithCron() {
String businessHoursCronExpression = "0 0 9-17 * * MON-FRI"; String businessHoursCronExpression = "0 0 9-17 * * MON-FRI";
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertySourcesPlaceholderConfigurer.class); BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertySourcesPlaceholderConfigurer.class);
@ -640,7 +499,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Test @Test
public void propertyPlaceholderWithInactiveCron() { void propertyPlaceholderWithInactiveCron() {
String businessHoursCronExpression = "-"; String businessHoursCronExpression = "-";
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertySourcesPlaceholderConfigurer.class); BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertySourcesPlaceholderConfigurer.class);
@ -657,24 +516,23 @@ public class ScheduledAnnotationBeanPostProcessorTests {
assertThat(postProcessor.getScheduledTasks().isEmpty()).isTrue(); assertThat(postProcessor.getScheduledTasks().isEmpty()).isTrue();
} }
@Test @ParameterizedTest
public void propertyPlaceholderWithFixedDelayInMillis() { @CsvSource({
propertyPlaceholderWithFixedDelay(false); "PropertyPlaceholderWithFixedDelay, 5000, 1000, 5000, 1000",
} "PropertyPlaceholderWithFixedDelay, PT5S, PT1S, 5000, 1000",
"PropertyPlaceholderWithFixedDelayInSeconds, 5000, 1000, 5000000, 1000000",
@Test "PropertyPlaceholderWithFixedDelayInSeconds, PT5S, PT1S, 5000, 1000"
public void propertyPlaceholderWithFixedDelayInDuration() { })
propertyPlaceholderWithFixedDelay(true); 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 processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertySourcesPlaceholderConfigurer.class); BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertySourcesPlaceholderConfigurer.class);
Properties properties = new Properties(); Properties properties = new Properties();
properties.setProperty("fixedDelay", (durationFormat ? "PT5S" : "5000")); properties.setProperty("fixedDelay", fixedDelay);
properties.setProperty("initialDelay", (durationFormat ? "PT1S" : "1000")); properties.setProperty("initialDelay", initialDelay);
placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties); placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties);
BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithFixedDelayTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(beanClass);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("placeholder", placeholderDefinition); context.registerBeanDefinition("placeholder", placeholderDefinition);
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
@ -696,28 +554,29 @@ public class ScheduledAnnotationBeanPostProcessorTests {
Method targetMethod = runnable.getMethod(); Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target); assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedDelay"); assertThat(targetMethod.getName()).isEqualTo("fixedDelay");
assertThat(task.getInitialDelay()).isEqualTo(1000L); assertSoftly(softly -> {
assertThat(task.getInterval()).isEqualTo(5000L); softly.assertThat(task.getInitialDelay()).as("initial delay").isEqualTo(expectedInitialDelay);
} softly.assertThat(task.getInterval()).as("interval").isEqualTo(expectedInterval);
});
@Test }
public void propertyPlaceholderWithFixedRateInMillis() {
propertyPlaceholderWithFixedRate(false); @ParameterizedTest
} @CsvSource({
"PropertyPlaceholderWithFixedRate, 3000, 1000, 3000, 1000",
@Test "PropertyPlaceholderWithFixedRate, PT3S, PT1S, 3000, 1000",
public void propertyPlaceholderWithFixedRateInDuration() { "PropertyPlaceholderWithFixedRateInSeconds, 3000, 1000, 3000000, 1000000",
propertyPlaceholderWithFixedRate(true); "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 processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertySourcesPlaceholderConfigurer.class); BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertySourcesPlaceholderConfigurer.class);
Properties properties = new Properties(); Properties properties = new Properties();
properties.setProperty("fixedRate", (durationFormat ? "PT3S" : "3000")); properties.setProperty("fixedRate", fixedRate);
properties.setProperty("initialDelay", (durationFormat ? "PT1S" : "1000")); properties.setProperty("initialDelay", initialDelay);
placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties); placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties);
BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithFixedRateTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(beanClass);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("placeholder", placeholderDefinition); context.registerBeanDefinition("placeholder", placeholderDefinition);
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
@ -739,12 +598,14 @@ public class ScheduledAnnotationBeanPostProcessorTests {
Method targetMethod = runnable.getMethod(); Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target); assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedRate"); assertThat(targetMethod.getName()).isEqualTo("fixedRate");
assertThat(task.getInitialDelay()).isEqualTo(1000L); assertSoftly(softly -> {
assertThat(task.getInterval()).isEqualTo(3000L); softly.assertThat(task.getInitialDelay()).as("initial delay").isEqualTo(expectedInitialDelay);
softly.assertThat(task.getInterval()).as("interval").isEqualTo(expectedInterval);
});
} }
@Test @Test
public void expressionWithCron() { void expressionWithCron() {
String businessHoursCronExpression = "0 0 9-17 * * MON-FRI"; String businessHoursCronExpression = "0 0 9-17 * * MON-FRI";
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(ExpressionWithCronTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(ExpressionWithCronTestBean.class);
@ -775,7 +636,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Test @Test
public void propertyPlaceholderForMetaAnnotation() { void propertyPlaceholderForMetaAnnotation() {
String businessHoursCronExpression = "0 0 9-17 * * MON-FRI"; String businessHoursCronExpression = "0 0 9-17 * * MON-FRI";
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertySourcesPlaceholderConfigurer.class); BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertySourcesPlaceholderConfigurer.class);
@ -808,7 +669,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Test @Test
public void nonVoidReturnType() { void nonVoidReturnType() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(NonVoidReturnTypeTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(NonVoidReturnTypeTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
@ -835,7 +696,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Test @Test
public void emptyAnnotation() { void emptyAnnotation() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(EmptyAnnotationTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(EmptyAnnotationTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
@ -845,7 +706,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Test @Test
public void invalidCron() throws Throwable { void invalidCron() throws Throwable {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(InvalidCronTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(InvalidCronTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
@ -855,7 +716,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Test @Test
public void nonEmptyParamList() { void nonEmptyParamList() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(NonEmptyParamListTestBean.class); BeanDefinition targetDefinition = new RootBeanDefinition(NonEmptyParamListTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
@ -865,70 +726,68 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
static class FixedDelayTestBean { static class FixedDelay {
@Scheduled(fixedDelay = 5000) @Scheduled(fixedDelay = 5000)
public void fixedDelay() { void fixedDelay() {
} }
} }
static class FixedDelayWithSecondsTimeUnitTestBean { static class FixedDelayInSeconds {
@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS) @Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
public void fixedDelay() { void fixedDelay() {
} }
} }
static class FixedDelayWithMinutesTimeUnitTestBean { static class FixedDelayInMinutes {
@Scheduled(fixedDelay = 3, timeUnit = TimeUnit.MINUTES) @Scheduled(fixedDelay = 3, timeUnit = TimeUnit.MINUTES)
public void fixedDelay() { void fixedDelay() {
} }
} }
static class FixedRateTestBean { static class FixedRate {
@Scheduled(fixedRate = 3000) @Scheduled(fixedRate = 3000)
public void fixedRate() { void fixedRate() {
} }
} }
static class FixedRateWithSecondsTimeUnitTestBean {
static class FixedRateInSeconds {
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS) @Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS)
public void fixedRate() { void fixedRate() {
} }
} }
static class FixedRateInMinutes {
static class FixedRateWithMinutesTimeUnitTestBean {
@Scheduled(fixedRate = 3, timeUnit = TimeUnit.MINUTES) @Scheduled(fixedRate = 3, timeUnit = TimeUnit.MINUTES)
public void fixedRate() { void fixedRate() {
} }
} }
static class FixedRateWithInitialDelayTestBean { static class FixedRateWithInitialDelay {
@Scheduled(fixedRate = 3000, initialDelay = 1000) @Scheduled(fixedRate = 3000, initialDelay = 1000)
public void fixedRate() { void fixedRate() {
} }
} }
static class FixedRateWithSecondsTimeUnitInitialDelayTestBean { static class FixedRateWithInitialDelayInSeconds {
@Scheduled(fixedRate = 3, initialDelay = 5, timeUnit = TimeUnit.SECONDS) @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) @Scheduled(fixedRate = 3, initialDelay = 1, timeUnit = TimeUnit.MINUTES)
public void fixedRate() { void fixedRate() {
} }
} }
@ -936,7 +795,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class SeveralFixedRatesWithSchedulesContainerAnnotationTestBean { static class SeveralFixedRatesWithSchedulesContainerAnnotationTestBean {
@Schedules({@Scheduled(fixedRate = 4000), @Scheduled(fixedRate = 4000, initialDelay = 2000)}) @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)
@Scheduled(fixedRate = 4000, initialDelay = 2000) @Scheduled(fixedRate = 4000, initialDelay = 2000)
public void fixedRate() { void fixedRate() {
} }
static SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean nestedProxy() { static SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean nestedProxy() {
@ -962,7 +821,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
@Scheduled(fixedRate = 4000) @Scheduled(fixedRate = 4000)
@Scheduled(fixedRate = 4000, initialDelay = 2000) @Scheduled(fixedRate = 4000, initialDelay = 2000)
public void fixedRate() { void fixedRate() {
} }
} }
@ -1006,7 +865,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class CronWithInvalidTimezoneTestBean { static class CronWithInvalidTimezoneTestBean {
@Scheduled(cron = "0 0 0-4,6-23 * * ?", zone = "FOO") @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"); throw new IOException("no no no");
} }
} }
@ -1017,7 +876,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class ProxiedCronTestBean { static class ProxiedCronTestBean {
@Scheduled(cron = "*/7 * * * * ?") @Scheduled(cron = "*/7 * * * * ?")
public void cron() throws IOException { void cron() throws IOException {
throw new IOException("no no no"); throw new IOException("no no no");
} }
} }
@ -1025,7 +884,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class ProxiedCronTestBeanDependent { static class ProxiedCronTestBeanDependent {
public ProxiedCronTestBeanDependent(ProxiedCronTestBean testBean) { ProxiedCronTestBeanDependent(ProxiedCronTestBean testBean) {
} }
} }
@ -1033,7 +892,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class NonVoidReturnTypeTestBean { static class NonVoidReturnTypeTestBean {
@Scheduled(cron = "0 0 9-17 * * MON-FRI") @Scheduled(cron = "0 0 9-17 * * MON-FRI")
public String cron() { String cron() {
return "oops"; return "oops";
} }
} }
@ -1042,7 +901,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class EmptyAnnotationTestBean { static class EmptyAnnotationTestBean {
@Scheduled @Scheduled
public void invalid() { void invalid() {
} }
} }
@ -1050,7 +909,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class InvalidCronTestBean { static class InvalidCronTestBean {
@Scheduled(cron = "abc") @Scheduled(cron = "abc")
public void invalid() { void invalid() {
} }
} }
@ -1058,7 +917,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class NonEmptyParamListTestBean { static class NonEmptyParamListTestBean {
@Scheduled(fixedRate = 3000) @Scheduled(fixedRate = 3000)
public void invalid(String oops) { void invalid(String oops) {
} }
} }
@ -1090,7 +949,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class MetaAnnotationFixedRateTestBean { static class MetaAnnotationFixedRateTestBean {
@EveryFiveSeconds @EveryFiveSeconds
public void checkForUpdates() { void checkForUpdates() {
} }
} }
@ -1098,7 +957,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class ComposedAnnotationFixedRateTestBean { static class ComposedAnnotationFixedRateTestBean {
@WaitASec(fixedRate = 5000) @WaitASec(fixedRate = 5000)
public void checkForUpdates() { void checkForUpdates() {
} }
} }
@ -1106,7 +965,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class MetaAnnotationCronTestBean { static class MetaAnnotationCronTestBean {
@Hourly @Hourly
public void generateReport() { void generateReport() {
} }
} }
@ -1114,23 +973,37 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class PropertyPlaceholderWithCronTestBean { static class PropertyPlaceholderWithCronTestBean {
@Scheduled(cron = "${schedules.businessHours}") @Scheduled(cron = "${schedules.businessHours}")
public void x() { void x() {
} }
} }
static class PropertyPlaceholderWithFixedDelayTestBean { static class PropertyPlaceholderWithFixedDelay {
@Scheduled(fixedDelayString = "${fixedDelay}", initialDelayString = "${initialDelay}") @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}") @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 { static class ExpressionWithCronTestBean {
@Scheduled(cron = "#{schedules.businessHours}") @Scheduled(cron = "#{schedules.businessHours}")
public void x() { void x() {
} }
} }
@ -1153,7 +1026,24 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class PropertyPlaceholderMetaAnnotationTestBean { static class PropertyPlaceholderMetaAnnotationTestBean {
@BusinessHours @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);
}
}
} }
} }

41
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 ==== The `@Scheduled` annotation
You can add the `@Scheduled` annotation to a method, along with trigger metadata. For 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, example, the following method is invoked every five seconds (5000 milliseconds) with a
meaning that the period is measured from the completion time of each preceding fixed delay, meaning that the period is measured from the completion time of each
invocation: preceding invocation.
[source,java,indent=0,subs="verbatim,quotes"] [source,java,indent=0,subs="verbatim,quotes"]
---- ----
@Scheduled(fixedDelay=5000) @Scheduled(fixedDelay = 5000)
public void doSomething() { public void doSomething() {
// something that should run periodically // something that should run periodically
} }
---- ----
If you need a fixed-rate execution, you can change the property name specified within [NOTE]
the annotation. The following method is invoked every five seconds (measured between the ====
successive start times of each invocation): 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"] [source,java,indent=0,subs="verbatim,quotes"]
---- ----
@Scheduled(fixedRate=5000) @Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS)
public void doSomething() { public void doSomething() {
// something that should run periodically // something that should run periodically
} }
---- ----
For fixed-delay and fixed-rate tasks, you can specify an initial delay by indicating the 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 amount of time to wait before the first execution of the method, as the following
`fixedRate` example shows: `fixedRate` example shows.
[source,java,indent=0,subs="verbatim,quotes"] [source,java,indent=0,subs="verbatim,quotes"]
---- ----
@Scheduled(initialDelay=1000, fixedRate=5000) @Scheduled(initialDelay = 1000, fixedRate = 5000)
public void doSomething() { public void doSomething() {
// something that should run periodically // 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 TIP: You can also use the `zone` attribute to specify the time zone in which the cron
expression is resolved. 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 arguments. If the method needs to interact with other objects from the application
context, those would typically have been provided through dependency injection. context, those would typically have been provided through dependency injection.

Loading…
Cancel
Save