Планирование в Java EE

Планирование в Java EE

1. обзор

В предыдущемarticle мы продемонстрировали, как планировать задачи в Spring с помощью аннотации* @Scheduled *__. В этой статье мы продемонстрируем, как добиться того же, используяthe timer service in a Java Enterprise Edition application для каждого случая, представленного в предыдущей статье.

2. Включить поддержку планирования

В приложении Java EE нет необходимости включать поддержку синхронизированных задач. Служба таймера является службой, управляемой контейнером, которая позволяет приложениям вызывать методы, запланированные для событий, основанных на времени. Например, приложение может запускать некоторые ежедневные отчеты в определенный час, чтобы генерировать статистику.

Есть два типа таймеров:

  • Programmatic timers: службу таймера можно внедрить в любой bean-компонент (кроме сеансового bean-компонента с отслеживанием состояния), а бизнес-логику следует поместить в метод, аннотированный@Timeout. Таймер может быть инициализирован методом, аннотированным@PostConstruct компонентов, или его также можно инициализировать, просто вызвав метод.

  • Automatic timers: бизнес-логика помещается в любой метод, помеченный@Schedule или@Schedules. Эти таймеры инициализируются сразу после запуска приложения.

Итак, давайте начнем с нашего первого примера.

3. Расписание задач с фиксированной задержкой

В Spring это делается просто с помощью аннотации@Scheduled(fixedDelay = 1000). В этом случае продолжительность между окончанием последнего выполнения и началом следующего выполнения является фиксированной. Задача всегда ждет, пока предыдущая не будет закончена.

Делать то же самое в Java 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. Расписание задач с фиксированной скоростью

Один из способов сделать это - использоватьtimer service, который вводится с использованием@Resource и настраивается в методе с аннотацией@PostConstruct. Метод, помеченный@Timeout, будет вызываться по истечении таймера.

Как упоминалось в предыдущей статье, начало выполнения задачи не дожидается завершения предыдущего выполнения. Эту опцию следует использовать, когда каждое выполнение задачи является независимым. Следующий фрагмент кода создает таймер, который срабатывает каждую секунду:

@Startup
@Singleton
public class ProgrammaticAtFixedRateTimerBean {

    @Inject
    Event 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 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. Расписание задачи с начальной задержкой

Если ваш сценарий использования требует, чтобы таймер запускался с задержкой, мы можем сделать это тоже. В этом случае Java EE позволяет использоватьtimer service. Давайте посмотрим на пример, в котором таймер имеет начальную задержку в 10 секунд, а затем срабатывает каждые пять секунд:

@Startup
@Singleton
public class ProgrammaticWithInitialFixedDelayTimerBean {

    @Inject
    Event 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, используемый в нашем примере, использует следующую сигнатуру createTimer(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. Заключение

В этой статье мы рассмотрели различные способыschedule tasks in the Java EE environment, используя в качестве отправной точки предыдущую статью, в которой образцы были выполнены с использованием Spring.

Примеры кода можно найти вGitHub repository.