spring_boot_starter_scheduling 0.1.3
spring_boot_starter_scheduling: ^0.1.3 copied to clipboard
Scheduling starter for spring_boot_dart: @Scheduled methods with fixed rate, fixed delay or cron expressions, a TaskScheduler bean, stopped with the context.
spring_boot_starter_scheduling #
Scheduled tasks for spring_boot_dart
applications, like Spring's @Scheduled: annotate a bean method and it runs
every few seconds, after each run with a pause, or at cron times. Tasks stop
when the context closes.
Installation #
dependencies:
spring_boot_dart: ^0.1.0
spring_boot_starter_scheduling: ^0.1.0
dev_dependencies:
build_runner: ^2.17.0
spring_core_dart_generator: ^0.1.0 # required: makes @Scheduled methods callable
dart run build_runner build
That is all: the starter is found automatically (no @EnableScheduling).
Quick example #
import 'package:spring_boot_starter_scheduling/spring_boot_starter_scheduling.dart';
@Component()
class Reports {
Reports(this.repository);
final ReportRepository repository;
@Scheduled(fixedRate: Duration(minutes: 5))
void refreshCache() => repository.refresh();
@Scheduled(fixedDelay: Duration(seconds: 30))
Future<void> syncWithServer() async => await repository.sync();
@Scheduled(cron: '0 0 9 * * MON-FRI') // 9:00 on working days
void morningDigest() => repository.sendDigest();
}
Start the application as usual (runApplication(args)); the tasks start
once the context is ready.
@Scheduled #
Give exactly one of cron, fixedRate or fixedDelay.
| Attribute | Meaning | Example |
|---|---|---|
fixedRate |
start every period (measured between starts) | fixedRate: Duration(minutes: 5) |
fixedDelay |
wait this long after each run completes | fixedDelay: Duration(seconds: 30) |
initialDelay |
wait before the first run (not with cron) |
initialDelay: Duration(seconds: 10) |
cron |
run at cron times; - switches the task off |
cron: '0 */15 * * * *' |
zone |
time zone of cron: leave out for local time, or 'UTC' |
zone: 'UTC' |
fixedRateString, fixedDelayString, initialDelayString |
the same as text, with ${...} placeholders and Spring Boot durations |
fixedRateString: r'${reports.rate:5m}' |
Rules:
- The method must be public and take no parameters. It may return a
Future: the run counts as finished when the future completes. - Several
@Scheduledon one method give several schedules. - Only singleton beans are scheduled (
@Scope('prototype')beans are ignored with a warning). - A wrong attribute (two kinds, a bad cron, an unknown zone, a missing
property) stops the startup with a message naming
Class.method. - The method is called on the bean as other beans see it, so AOP advice
(
@Around,@Cacheable, ...) applies.
Cron expressions #
Six fields, like Spring (not five like Unix cron):
second minute hour day-of-month month day-of-week
| Expression | Runs |
|---|---|
0 * * * * * |
every minute, at second 0 |
*/10 * * * * * |
every 10 seconds |
0 */15 * * * * |
every 15 minutes |
0 0 * * * * |
every hour |
0 0 9-17 * * MON-FRI |
every hour from 9 to 17 on working days |
0 30 8 * * * |
8:30 every day |
0 0 0 1 * * |
midnight on the 1st of each month |
0 0 18 L * * |
18:00 on the last day of the month |
0 0 12 LW * * |
noon on the last working day of the month |
0 0 10 15W * * |
10:00 on the working day nearest to the 15th |
0 0 20 ? * FRIL |
20:00 on the last Friday of the month |
0 0 9 ? * MON#1 |
9:00 on the first Monday of the month |
0 0 0 25 DEC * |
midnight on December 25th |
@hourly, @daily (@midnight), @weekly, @monthly, @yearly (@annually) |
the usual macros |
- Values:
*,5, lists1,15,30, ranges9-17, steps*/5,0-30/10,5/15(from 5, every 15). MonthsJAN-DEC, daysSUN-SAT(0 and 7 are Sunday), case-insensitive.?means "any" in the day fields. - As in Spring, a day must match both day fields, and ranges cannot wrap
around (
FRI-MONis an error: writeFRI-SAT,SUN-MON). - Local time follows the wall clock: a time inside the hour skipped when clocks go forward runs at the first valid time after it; a time inside the hour repeated when clocks go back runs once.
Check an expression in code:
final cron = CronExpression.parse('0 0 9 * * MON-FRI');
print(cron.next(DateTime.now())); // the next run
CronExpression.isValid('0 0 25 * * *'); // false: hour 25
Configuration from properties #
Every text attribute resolves placeholders, so schedules can live in
application.properties and change per profile:
@Scheduled(fixedRateString: r'${reports.rate:5m}')
void refresh() { ... }
@Scheduled(cron: r'${reports.cleanup-cron:-}') // off unless configured
void cleanup() { ... }
# lib/resources/application.properties
reports.rate=10m
reports.cleanup-cron=0 0 3 * * *
Durations: 500 (milliseconds), 10s, 5m, 2h, 1d, PT1H30M.
| Property | Default | Meaning |
|---|---|---|
spring.task.scheduling.enabled |
true |
false switches scheduling off (no TaskScheduler, no tasks) |
Scheduling in code: TaskScheduler #
Inject the TaskScheduler bean to schedule work yourself:
@Service()
class Reminders {
Reminders(this.scheduler);
final TaskScheduler scheduler;
ScheduledTask remindAt(DateTime at, String text) =>
scheduler.schedule(() => notify(text), OneShotTrigger(at));
ScheduledTask poll() => scheduler.scheduleWithFixedDelay(
checkInbox,
const Duration(seconds: 30),
);
}
final task = reminders.poll();
print(task.nextExecution);
task.cancel();
Triggers: PeriodicTrigger (fixed rate or delay), CronTrigger,
OneShotTrigger, or your own Trigger. Declare your own TaskScheduler
bean to replace the default TimerTaskScheduler.
Asynchronous tasks and overlapping #
Everything runs on Dart's event loop, so a task never runs twice at the same time:
fixedDelay: the pause starts when the run (itsFuture) completes.fixedRate: when a run takes longer than the period, the next run starts right after it, once; missed runs are not caught up.cron: the next time is computed after the run, so times missed during a long run are skipped.
Errors #
An exception (thrown or from the returned Future) is logged at error
level to the spring.scheduling logger, with the task name; the task keeps
its schedule. Handle errors in the method if you need retries or alerts.
Testing #
Scheduled methods run in springBootTest too. To keep tests
deterministic, either switch scheduling off and call the methods directly:
final app = springBootTest(
springApplication,
properties: {'spring.task.scheduling.enabled': false},
);
test('refresh', () => app.getBean<Reports>().refreshCache());
or replace the scheduler with one on a controlled clock
(TimerTaskScheduler(now: ..., createTimer: ...)) through BeanOverride.
ScheduledAnnotationBeanPostProcessor.scheduledTasks lists what is
scheduled.
Hot reload #
Tasks belong to the context: a hot-reload restart closes it (cancelling every task) and the new context schedules the methods of the new code.
Differences from Spring #
- No
@EnableScheduling: the dependency switches scheduling on;spring.task.scheduling.enabled=falseswitches it off. - Time zones: local time or UTC only (Dart has no time zone database).
- Durations are
Durations instead of numbers plustimeUnit. - Not yet:
@Async,SchedulingConfigurer, scheduling of prototype beans.