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) {