Перейти к основному содержимому

Планирование в Джакарте EE

· 5 мин. чтения

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 .