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
This commit is contained in:
Sam Brannen
2021-08-25 20:46:51 +02:00
parent e99b43b91e
commit bd72e4498b
4 changed files with 249 additions and 321 deletions

View File

@@ -48,6 +48,7 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
* @author Dave Syer
* @author Chris Beams
* @author Victor Brown
* @author Sam Brannen
* @since 3.0
* @see EnableScheduling
* @see ScheduledAnnotationBeanPostProcessor
@@ -103,63 +104,74 @@ public @interface Scheduled {
String zone() default "";
/**
* Execute the annotated method with a fixed period between the
* end of the last invocation and the start of the next.
* Using milliseconds by default with timeUnit().
* Execute the annotated method with a fixed period between the end of the
* last invocation and the start of the next.
* <p>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.
* <p>The time unit is milliseconds by default but can be overridden via
* {@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
* @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.
* <p>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.
* <p>The time unit is milliseconds by default but can be overridden via
* {@link #timeUnit}.
* @return the period as a String value &mdash; 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().
* <p>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
* <p>The time unit is milliseconds by default but can be overridden via
* {@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
* @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}.
* <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;

View File

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

View File

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