1. Обзор
В предыдущей статье мы продемонстрировали, как планировать задачи в Spring с помощью @Scheduled.
**
`` аннотация. В этой статье мы покажем, как добиться того же, используя службу таймера в приложении Jakarta EE** для каждого случая, представленного в предыдущей статье.
2. Включите поддержку планирования
В приложении Jakarta EE нет необходимости включать поддержку задач на время. Служба таймера — это служба, управляемая контейнером, которая позволяет приложениям вызывать методы, запланированные для событий, зависящих от времени. Например, приложению может потребоваться запускать некоторые ежедневные отчеты в определенный час, чтобы генерировать статистику.
Существует два типа таймеров:
- Программные таймеры : служба таймера может быть внедрена в любой компонент (кроме сеансового компонента с отслеживанием состояния), а бизнес-логика должна быть помещена в метод, аннотированный
@Timeout
. Таймер может быть инициализирован методом, аннотированным@PostConstruct
bean-компонентов, или он также может быть инициализирован простым вызовом метода.
- Автоматические таймеры : бизнес-логика размещается в любом методе, аннотированном
@Schedule
или@Schedules
. Эти таймеры инициализируются сразу после запуска приложения.
Итак, давайте начнем с нашего первого примера.
3. Расписание задачи с фиксированной задержкой
В Spring это делается просто с помощью аннотации @Scheduled(fixedDelay = 1000)
. В этом случае продолжительность между окончанием последнего выполнения и началом следующего выполнения является фиксированной. Задача всегда ожидает завершения предыдущей.
Сделать то же самое в Jakarta EE немного сложнее, потому что нет аналогичного встроенного механизма, тем не менее, аналогичный сценарий можно реализовать с небольшим количеством дополнительного кода. Давайте посмотрим, как это делается:
@Singleton
public class FixedTimerBean {
@EJB
private WorkerBean workerBean;
@Lock(LockType.READ)
@Schedule(second = "*/5", minute = "*", hour = "*", persistent = false)
public void atSchedule() throws InterruptedException {
workerBean.doTimerWork();
}
}
@Singleton
public class WorkerBean {
private AtomicBoolean busy = new AtomicBoolean(false);
@Lock(LockType.READ)
public void doTimerWork() throws InterruptedException {
if (!busy.compareAndSet(false, true)) {
return;
}
try {
Thread.sleep(20000L);
} finally {
busy.set(false);
}
}
}
Как видите, таймер должен срабатывать каждые пять секунд. Однако метод, запущенный в нашем случае, имитировал 20-секундное время отклика при вызове sleep()
в текущем Thread
.
Как следствие, контейнер будет продолжать вызывать doTimerWork()
каждые пять секунд, но условие, поставленное в начале метода, busy.compareAndSet(false, true),
немедленно вернется, если предыдущий вызов не завершился. При этом мы гарантируем, что следующая задача будет выполнена только после завершения предыдущей.
4. Расписание задач с фиксированной скоростью
Один из способов сделать это — использовать службу таймера, которая внедряется с помощью @Resource
и настраивается в методе, аннотированном @PostConstruct
. Метод с аннотацией @Timeout
будет вызываться по истечении времени таймера.
Как упоминалось в предыдущей статье, начало выполнения задачи не ожидает завершения предыдущего выполнения. Эту опцию следует использовать, когда каждое выполнение задачи является независимым. Следующий фрагмент кода создает таймер, который срабатывает каждую секунду:
@Startup
@Singleton
public class ProgrammaticAtFixedRateTimerBean {
@Inject
Event<TimerEvent> event;
@Resource
TimerService timerService;
@PostConstruct
public void initialize() {
timerService.createTimer(0,1000, "Every second timer with no delay");
}
@Timeout
public void programmaticTimout(Timer timer) {
event.fire(new TimerEvent(timer.getInfo().toString()));
}
}
Другой способ — использовать аннотацию @Scheduled
. В следующем фрагменте кода мы запускаем таймер каждые пять секунд:
@Startup
@Singleton
public class ScheduleTimerBean {
@Inject
Event<TimerEvent> event;
@Schedule(hour = "*", minute = "*", second = "*/5", info = "Every 5 seconds timer")
public void automaticallyScheduled(Timer timer) {
fireEvent(timer);
}
private void fireEvent(Timer timer) {
event.fire(new TimerEvent(timer.getInfo().toString()));
}
}
5. Расписание задачи с начальной задержкой
Если ваш сценарий использования требует, чтобы таймер запускался с задержкой, мы также можем это сделать. В этом случае Jakarta EE позволяет использовать службу таймера . Давайте посмотрим на пример, где таймер имеет начальную задержку 10 секунд, а затем срабатывает каждые пять секунд:
@Startup
@Singleton
public class ProgrammaticWithInitialFixedDelayTimerBean {
@Inject
Event<TimerEvent> event;
@Resource
TimerService timerService;
@PostConstruct
public void initialize() {
timerService.createTimer(10000, 5000, "Delay 10 seconds then every 5 seconds timer");
}
@Timeout
public void programmaticTimout(Timer timer) {
event.fire(new TimerEvent(timer.getInfo().toString()));
}
}
Метод createTimer
, используемый в нашем образце, использует следующую сигнатуру c reateTimer(long initialDuration, long intervalDuration, java.io.Serializable info),
где initialDuration
— это количество миллисекунд, которое должно пройти до первого уведомления об истечении срока действия таймера, а intervalDuration
— это количество миллисекунды, которые должны пройти между уведомлениями об истечении срока действия таймера.
В этом примере мы используем initialDuration
, равное 10 секундам, и intervalDuration
, равное пяти секундам. Задача будет выполняться в первый раз после значения initialDuration
и будет продолжать выполняться в соответствии с intervalDuration
.
6. Планирование задачи с использованием выражений Cron
Все планировщики, которые мы видели, как программные, так и автоматические, позволяют использовать выражения cron. Давайте посмотрим пример:
@Schedules ({
@Schedule(dayOfMonth="Last"),
@Schedule(dayOfWeek="Fri", hour="23")
})
public void doPeriodicCleanup() { ... }
В этом примере метод doPeriodicCleanup()
будет вызываться каждую пятницу в 23:00 и в последний день месяца.
7. Заключение
В этой статье мы рассмотрели различные способы планирования задач в среде Jakarta EE , используя в качестве отправной точки предыдущую статью, где примеры были сделаны с использованием Spring.
Образцы кода можно найти в репозитории GitHub .