Skip to content

Commit

Permalink
Support for one-time tasks with just @scheduled(initialDelay=...)
Browse files Browse the repository at this point in the history
Closes gh-31211
  • Loading branch information
jhoeller committed Sep 13, 2023
1 parent e63e3a6 commit 8f6c56f
Show file tree
Hide file tree
Showing 10 changed files with 312 additions and 70 deletions.
19 changes: 15 additions & 4 deletions framework-docs/modules/ROOT/pages/integration/scheduling.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ For example, the previous example can also be written as follows.

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).
successive start times of each invocation):

[source,java,indent=0,subs="verbatim,quotes"]
----
Expand All @@ -356,9 +356,9 @@ successive start times of each invocation).
}
----

For fixed-delay and fixed-rate tasks, you can specify an initial delay by indicating the
amount of time to wait before the first execution of the method, as the following
`fixedRate` example shows.
For fixed-delay and fixed-rate tasks, you can specify an initial delay by indicating
the amount of time to wait before the first execution of the method, as the following
`fixedRate` example shows:

[source,java,indent=0,subs="verbatim,quotes"]
----
Expand All @@ -368,6 +368,17 @@ amount of time to wait before the first execution of the method, as the followin
}
----

For one-time tasks, you can just specify an initial delay by indicating the amount
of time to wait before the intended execution of the method:

[source,java,indent=0,subs="verbatim,quotes"]
----
@Scheduled(initialDelay = 1000)
public void doSomething() {
// something that should run only once
}
----

If simple periodic scheduling is not expressive enough, you can provide a
xref:integration/scheduling.adoc#scheduling-cron-expression[cron expression].
The following example runs only on weekdays:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

/**
* Annotation that marks a method to be scheduled. Exactly one of the
* {@link #cron}, {@link #fixedDelay}, or {@link #fixedRate} attributes
* must be specified.
* Annotation that marks a method to be scheduled. For periodic tasks, exactly one
* of the {@link #cron}, {@link #fixedDelay}, or {@link #fixedRate} attributes
* must be specified, and additionally an optional {@link #initialDelay}.
* For a one-time task, it is sufficient to just specify an {@link #initialDelay}.
*
* <p>The annotated method must not accept arguments. It will typically have
* a {@code void} return type; if not, the returned value will be ignored
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.FixedDelayTask;
import org.springframework.scheduling.config.FixedRateTask;
import org.springframework.scheduling.config.OneTimeTask;
import org.springframework.scheduling.config.ScheduledTask;
import org.springframework.scheduling.config.ScheduledTaskHolder;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
Expand Down Expand Up @@ -393,8 +394,7 @@ private void processScheduledAsync(Scheduled scheduled, Method method, Object be
private void processScheduledTask(Scheduled scheduled, Runnable runnable, Method method, Object bean) {
try {
boolean processedSchedule = false;
String errorMessage =
"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
String errorMessage = "Exactly one of the 'cron', 'fixedDelay' or 'fixedRate' attributes is required";

Set<ScheduledTask> tasks = new LinkedHashSet<>(4);

Expand Down Expand Up @@ -442,18 +442,15 @@ private void processScheduledTask(Scheduled scheduled, Runnable runnable, Method
}

// At this point we don't need to differentiate between initial delay set or not anymore
if (initialDelay.isNegative()) {
initialDelay = Duration.ZERO;
}
Duration delayToUse = (initialDelay.isNegative() ? Duration.ZERO : initialDelay);

// Check fixed delay
Duration fixedDelay = toDuration(scheduled.fixedDelay(), scheduled.timeUnit());
if (!fixedDelay.isNegative()) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, delayToUse)));
}

String fixedDelayString = scheduled.fixedDelayString();
if (StringUtils.hasText(fixedDelayString)) {
if (this.embeddedValueResolver != null) {
Expand All @@ -469,7 +466,7 @@ private void processScheduledTask(Scheduled scheduled, Runnable runnable, Method
throw new IllegalArgumentException(
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");
}
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, delayToUse)));
}
}

Expand All @@ -478,7 +475,7 @@ private void processScheduledTask(Scheduled scheduled, Runnable runnable, Method
if (!fixedRate.isNegative()) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, delayToUse)));
}
String fixedRateString = scheduled.fixedRateString();
if (StringUtils.hasText(fixedRateString)) {
Expand All @@ -495,12 +492,16 @@ private void processScheduledTask(Scheduled scheduled, Runnable runnable, Method
throw new IllegalArgumentException(
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");
}
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, delayToUse)));
}
}

// Check whether we had any attribute set
Assert.isTrue(processedSchedule, errorMessage);
if (!processedSchedule) {
if (initialDelay.isNegative()) {
throw new IllegalArgumentException("One-time task only supported with specified initial delay");
}
tasks.add(this.registrar.scheduleOneTimeTask(new OneTimeTask(runnable, delayToUse)));
}

// Finally register the scheduled tasks
synchronized (this.scheduledTasks) {
Expand Down Expand Up @@ -548,7 +549,13 @@ protected Runnable createRunnable(Object target, Method method) {
}

private static Duration toDuration(long value, TimeUnit timeUnit) {
return Duration.of(value, timeUnit.toChronoUnit());
try {
return Duration.of(value, timeUnit.toChronoUnit());
}
catch (Exception ex) {
throw new IllegalArgumentException(
"Unsupported unit " + timeUnit + " for value \"" + value + "\": " + ex.getMessage(), ex);
}
}

private static Duration toDuration(String value, TimeUnit timeUnit) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.scheduling.config;

import java.time.Duration;

import org.springframework.util.Assert;

/**
* {@link Task} implementation defining a {@code Runnable} with an initial delay.
*
* @author Juergen Hoeller
* @since 6.1
*/
public class DelayedTask extends Task {

private final Duration initialDelay;


/**
* Create a new {@code DelayedTask}.
* @param runnable the underlying task to execute
* @param initialDelay the initial delay before execution of the task
*/
public DelayedTask(Runnable runnable, Duration initialDelay) {
super(runnable);
Assert.notNull(initialDelay, "InitialDelay must not be null");
this.initialDelay = initialDelay;
}

/**
* Copy constructor.
*/
DelayedTask(DelayedTask task) {
super(task.getRunnable());
Assert.notNull(task, "DelayedTask must not be null");
this.initialDelay = task.getInitialDelayDuration();
}


/**
* Return the initial delay before first execution of the task.
*/
public Duration getInitialDelayDuration() {
return this.initialDelay;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,4 @@ public FixedRateTask(Runnable runnable, Duration interval, Duration initialDelay
super(task);
}


}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -31,12 +31,10 @@
* @see ScheduledTaskRegistrar#addFixedRateTask(IntervalTask)
* @see ScheduledTaskRegistrar#addFixedDelayTask(IntervalTask)
*/
public class IntervalTask extends Task {
public class IntervalTask extends DelayedTask {

private final Duration interval;

private final Duration initialDelay;


/**
* Create a new {@code IntervalTask}.
Expand Down Expand Up @@ -79,28 +77,20 @@ public IntervalTask(Runnable runnable, Duration interval) {
* @since 6.0
*/
public IntervalTask(Runnable runnable, Duration interval, Duration initialDelay) {
super(runnable);
super(runnable, initialDelay);
Assert.notNull(interval, "Interval must not be null");
Assert.notNull(initialDelay, "InitialDelay must not be null");

this.interval = interval;
this.initialDelay = initialDelay;
}

/**
* Copy constructor.
*/
IntervalTask(IntervalTask task) {
super(task.getRunnable());
Assert.notNull(task, "IntervalTask must not be null");

super(task);
this.interval = task.getIntervalDuration();
this.initialDelay = task.getInitialDelayDuration();
}




/**
* Return how often in milliseconds the task should be executed.
* @deprecated as of 6.0, in favor of {@link #getIntervalDuration()}
Expand All @@ -124,15 +114,16 @@ public Duration getIntervalDuration() {
*/
@Deprecated(since = "6.0")
public long getInitialDelay() {
return this.initialDelay.toMillis();
return getInitialDelayDuration().toMillis();
}

/**
* Return the initial delay before first execution of the task.
* @since 6.0
*/
@Override
public Duration getInitialDelayDuration() {
return this.initialDelay;
return super.getInitialDelayDuration();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.scheduling.config;

import java.time.Duration;

/**
* {@link Task} implementation defining a {@code Runnable} with an initial delay.
*
* @author Juergen Hoeller
* @since 6.1
* @see ScheduledTaskRegistrar#addOneTimeTask(DelayedTask)
*/
public class OneTimeTask extends DelayedTask {

/**
* Create a new {@code DelayedTask}.
* @param runnable the underlying task to execute
* @param initialDelay the initial delay before execution of the task
*/
public OneTimeTask(Runnable runnable, Duration initialDelay) {
super(runnable, initialDelay);
}

OneTimeTask(DelayedTask task) {
super(task);
}

}

0 comments on commit 8f6c56f

Please sign in to comment.