Skip to content

Commit

Permalink
Revise and document TimeUnit support in @scheduled
Browse files Browse the repository at this point in the history
This commit also fixes a bug introduced in commit e99b43b, where
java.time.Duration strings were converted to milliseconds and then
converted again using the configured TimeUnit.

See spring-projectsgh-27309
  • Loading branch information
sbrannen authored and lxbzmy committed Mar 26, 2022
1 parent bbcb4b6 commit 3f7d017
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 325 deletions.
Expand Up @@ -48,6 +48,7 @@
* @author Dave Syer
* @author Chris Beams
* @author Victor Brown
* @author Sam Brannen
* @since 3.0
* @see EnableScheduling
* @see ScheduledAnnotationBeanPostProcessor
Expand Down Expand Up @@ -103,63 +104,74 @@
String zone() default "";

/**
* Execute the annotated method with a fixed period between the
* end of the last invocation and the start of the next.
* Using milliseconds by default with timeUnit().
* Execute the annotated method with a fixed period between the end of the
* last invocation and the start of the next.
* <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;

Expand Down
Expand Up @@ -96,6 +96,7 @@
* @author Chris Beams
* @author Elizabeth Chatman
* @author Victor Brown
* @author Sam Brannen
* @since 3.0
* @see Scheduled
* @see EnableScheduling
Expand Down Expand Up @@ -385,7 +386,7 @@ public Object postProcessAfterInitialization(Object bean, String beanName) {

/**
* Process the given {@code @Scheduled} method declaration on the given bean.
* @param scheduled the @Scheduled annotation
* @param scheduled the {@code @Scheduled} annotation
* @param method the method that the annotation has been declared on
* @param bean the target bean instance
* @see #createRunnable(Object, Method)
Expand All @@ -400,7 +401,7 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
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");
Expand All @@ -409,7 +410,7 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
}
if (StringUtils.hasLength(initialDelayString)) {
try {
initialDelay = TimeUnit.MILLISECONDS.convert(parseDelayAsLong(initialDelayString), scheduled.timeUnit());
initialDelay = convertToMillis(initialDelayString, scheduled.timeUnit());
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
Expand Down Expand Up @@ -448,7 +449,7 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
}

// Check fixed delay
long fixedDelay = TimeUnit.MILLISECONDS.convert(scheduled.fixedDelay(), scheduled.timeUnit());
long fixedDelay = convertToMillis(scheduled.fixedDelay(), scheduled.timeUnit());
if (fixedDelay >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
Expand All @@ -464,7 +465,7 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
try {
fixedDelay = TimeUnit.MILLISECONDS.convert(parseDelayAsLong(fixedDelayString), scheduled.timeUnit());
fixedDelay = convertToMillis(fixedDelayString, scheduled.timeUnit());
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
Expand All @@ -475,7 +476,7 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
}

// Check fixed rate
long fixedRate = TimeUnit.MILLISECONDS.convert(scheduled.fixedRate(), scheduled.timeUnit());
long fixedRate = convertToMillis(scheduled.fixedRate(), scheduled.timeUnit());
if (fixedRate >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
Expand All @@ -490,7 +491,7 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
try {
fixedRate = TimeUnit.MILLISECONDS.convert(parseDelayAsLong(fixedRateString), scheduled.timeUnit());
fixedRate = convertToMillis(fixedRateString, scheduled.timeUnit());
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
Expand Down Expand Up @@ -530,11 +531,19 @@ protected Runnable createRunnable(Object target, Method method) {
return new ScheduledMethodRunnable(target, invocableMethod);
}

private static long parseDelayAsLong(String value) throws RuntimeException {
if (value.length() > 1 && (isP(value.charAt(0)) || isP(value.charAt(1)))) {
private static long convertToMillis(long value, TimeUnit timeUnit) {
return TimeUnit.MILLISECONDS.convert(value, timeUnit);
}

private static long convertToMillis(String value, TimeUnit timeUnit) {
if (isDurationString(value)) {
return Duration.parse(value).toMillis();
}
return Long.parseLong(value);
return convertToMillis(Long.parseLong(value), timeUnit);
}

private static boolean isDurationString(String value) {
return (value.length() > 1 && (isP(value.charAt(0)) || isP(value.charAt(1))));
}

private static boolean isP(char ch) {
Expand Down

0 comments on commit 3f7d017

Please sign in to comment.