Java Platform, Enterprise Edition (Java EE) 8
Учебник по Java EE

Назад Вперёд Содержание

Использование сервиса таймера

Приложения, моделирующие бизнес-процессы, часто полагаются на синхронизированные уведомления. Сервис таймера контейнера EJB позволяет планировать уведомления по времени для всех типов EJB, за исключением компонентов EJB с сохранением состояния. Вы можете запланировать своевременное уведомление в соответствии с расписанием календаря, в определённое время, по истечении определённого времени или через определённые интервалы времени. Например, вы можете настроить таймеры на срабатывание в 10:30 утра 23 мая, через 30 дней или каждые 12 часов.

Таймеры Enterprise-бинов являются либо программными, либо автоматическими. Программные таймеры устанавливаются путём явного вызова одного из методов создания таймера интерфейса TimerService. Автоматические таймеры создаются после успешного развёртывания Enterprise-бина, содержащего метод, аннотированный javax.ejb.Schedule или javax.ejb.Schedules.

Создание календарных выражений таймера

Таймеры могут быть установлены в соответствии с календарным расписанием, выраженным с использованием синтаксиса, аналогичного утилите UNIX cron. Как программные, так и автоматические таймеры могут использовать календарные выражения таймера. Таблица 37-1 показывает атрибуты календарных выражений таймера.

Таблица 37-1 Атрибуты таймера на основе календаря

Атрибут

Описание

Значение по умолчанию

Допустимые значения и примеры

second

Одна или несколько секунд в течение минуты

0

0 to 59. Например: second="30".

minute

Одна или несколько минут в течение часа

0

0 to 59. Например: minute="15".

hour

Один или несколько часов в течение дня

0

0 to 23. Например: hour="13".

dayOfWeek

Один или несколько дней в течение недели

*

0 до 7 (0 и 7 относятся к воскресенью). Например: dayOfWeek = "3".

Sun, Mon, Tue, Wed, Thu, Fri, Sat. Например: dayOfWeek="Mon".

dayOfMonth

Один или несколько дней в течение месяца

*

1 to 31. Например: dayOfMonth="15".

–7 до –1 (отрицательное число означает n-й день или дни до конца месяца). Например: dayOfMonth="- 3".

Last. Например: dayOfMonth="Last".

[1st, 2nd, 3rd, 4th, 5th, Last] [Sun, Mon, Tue, Wed, Thu, Fri, Sat]. Например: dayOfMonth="2nd Fri".

month

Один или несколько месяцев в течение года

*

1 to 12. Например: month = "7".

Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec. Например: month="July".

year

Конкретный календарный год

*

Четырёхзначный календарный год. Например: year="2011".

Указание нескольких значений в календарных выражениях

Вы можете указать несколько значений в календарных выражениях, как описано в следующих разделах.

Использование подстановочных знаков в календарных выражениях

Установка атрибута в символ звездочки (*) представляет все допустимые значения для атрибута.

Следующее выражение представляет каждую минуту:

minute="*"

Следующее выражение представляет каждый день недели:

dayOfWeek="*"

Указание списка значений

Чтобы указать два значения и более для атрибута, используйте запятую (,) для разделения значений. Диапазон значений допускается как часть списка. Подстановочные знаки и интервалы, однако, не допускаются.

Дубликаты в списке игнорируются.

Следующее выражение устанавливает день недели во вторник и четверг:

dayOfWeek="Tue, Thu"

Следующее выражение представляет 4:00 утра, каждый час с 9:00 утра до 5:00 вечера. используя диапазон, и 10:00 вечера:

hour="4,9-17,22"

Указание диапазона значений

Используйте тире (-), чтобы указать диапазон значений для атрибута. Члены диапазона не могут быть подстановочными знаками, списками или интервалами. Диапазон в форме x-x эквивалентен однозначному выражению x. Диапазон в форме xy ​​, где x больше, чем y, эквивалентен выражению x-maximumvalue, maximumvalue-у. Таким образом, выражение начинается с x, переносится на начало допустимых значений и продолжается до y.

Следующее выражение представляет с 9:00 до 17:00:

hour="9-17"

Следующее выражение представляет период с пятницы до понедельника:

dayOfWeek="5-1"

Следующее выражение представляет период с двадцать пятого дня месяца до конца месяца и с начала месяца до пятого дня месяца:

dayOfMonth="25-5"

Это эквивалентно следующему выражению:

dayOfMonth="25-Last,1-5"

Указание интервалов

Косая черта (/) ограничивает атрибут начальной точкой и интервалом и используется для указания каждых N секунд, минут или часов в течение минуты, часа или дня. Для выражения вида x/y x представляет начальную точку, а y представляет интервал. Подстановочный знак может использоваться в позиции x интервала и эквивалентен установке x в 0.

Интервалы могут быть установлены только для атрибутов second, minute и hour.

Следующее выражение представляет каждые 10 минут в течение часа:

minute="*/10"

Это эквивалентно:

minute="0,10,20,30,40,50"

Следующее выражение представляет каждые 2 часа, начиная с полудня:

hour="12/2"

Программные таймеры

Когда срабатывает программный таймер, контейнер вызывает метод, аннотированный @Timeout в классе реализации компонента. Метод @Timeout содержит бизнес-логику, которая обрабатывает событие срабатывания.

Метод @Timeout

Методы, аннотированные @Timeout в классе Enterprise-бина, должны возвращать void и дополнительно принимать объект javax.ejb.Timer в качестве единственного параметра. Они не могут генерировать исключения приложений:

@Timeout
public void timeout(Timer timer) {
    System.out.println("TimerBean: timeout occurred");
}

Создание программных таймеров

Чтобы создать таймер, компонент вызывает один из методов create интерфейса TimerService. Эти методы позволяют создавать таймеры однократного действия, интервалов или календаря.

Для таймеров однократного действия или с интервалом срабатывание таймера может быть выражено либо как длительность, либо как абсолютное время. Длительность выражается в виде количества миллисекунд до срабатывания события тайм-аута. Чтобы указать абсолютное время, создайте объект java.util.Date и передайте его методу TimerService.createSingleActionTimer или TimerService.createTimer.

Следующий код устанавливает программный таймер, который срабатывает через 1 минуту (60000 миллисекунд):

long duration = 60000;
Timer timer =
    timerService.createSingleActionTimer(duration, new TimerConfig());

Следующий код устанавливает программный таймер, который срабатывает в 12:05. 1 мая 2015 г., указанное как java.util.Date:

SimpleDateFormatter formatter =
    new SimpleDateFormatter("MM/dd/yyyy 'at' HH:mm");
Date date = formatter.parse("05/01/2015 at 12:05");
Timer timer = timerService.createSingleActionTimer(date, new TimerConfig());

Для календарных таймеров срок действия таймера выражается в виде объекта javax.ejb.ScheduleExpression, передаваемого в качестве параметра методу TimerService.createCalendarTimer. Класс ScheduleExpression представляет календарные выражения таймера и имеет методы, соответствующие атрибутам, описанным в Создание календарных выражений таймера.

Следующий код создаёт программный таймер с использованием вспомогательного класса ScheduleExpression:

ScheduleExpression schedule = new ScheduleExpression();
schedule.dayOfWeek("Mon");
schedule.hour("12-17, 23");
Timer timer = timerService.createCalendarTimer(schedule);

Подробнее о сигнатурах метода см. документацию API TimerService по ссылке http://docs.oracle.com/javaee/7/api/javax/ejb/TimerService.html.

Компонент, описанный в Примере timersession, создаёт таймер следующим образом:

Timer timer = timerService.createTimer(intervalDuration,
        "Created new programmatic timer");

В примере timersession метод, вызывающий createTimer, вызывается в бизнес-методе, который вызывается клиентом.

Таймеры являются персистентным по умолчанию. Если сервер выключен или выходит из строя, персистентные таймеры сохраняются и снова становятся активными при перезапуске сервера. Если время срабатывания персистентного таймера наступает, когда сервер не работает, контейнер будет вызывать метод @Timeout при перезапуске сервера.

Неперсистентные программные таймеры создаются путём вызова TimerConfig.setPersistent(false) и передачи объекта TimerConfig одному из методов создания таймера.

Параметры Date и long методов createTimer представляют время с разрешением в миллисекундах. Однако, поскольку сервис таймера не предназначен для приложений реального времени, вызов Callback-метод @Timeout может не выполняться с точностью до миллисекунды. Сервис таймера предназначен для бизнес-приложений, которые обычно измеряют время в часах, днях или дольше.

Автоматические таймеры

Автоматические таймеры создаются контейнером EJB при развёртывании Enterprise-бина, содержащего методы, аннотированные @Schedule или @Schedules. В Enterprise-бине может быть несколько методов автоматического тайм-аута, в отличие от программного таймера, который позволяет использовать только один метод с аннотацией @Timeout в классе Enterprise-бина.

Автоматические таймеры можно настроить с помощью аннотаций или дескриптора развёртывания ejb-jar.xml.

Добавление аннотации @Schedule к Enterprise-бину помечает этот метод как метод тайм-аута в соответствии с календарным расписанием, указанным в атрибутах @Schedule.

Аннотация @Schedule содержит элементы, которые соответствуют выражениям календаря, подробно изложенным в Создание календарных выражений таймера и persistent, info и timezone.

Необязательный элемент persistent принимает логическое значение и используется для указания того, должен ли автоматический таймер выдерживать перезапуск сервера или сбой. По умолчанию все автоматические таймеры являются персистентными.

Необязательный элемент timezone используется для указания того, что автоматический таймер связан с конкретным часовым поясом. Если установлено, этот элемент будет оценивать все выражения таймера относительно указанного часового пояса, независимо от часового пояса, в котором работает контейнер EJB. По умолчанию все установленные автоматические таймеры относятся к часовому поясу по умолчанию на сервере.

Необязательный элемент info используется для установки информационного описания таймера. Информация о таймере может быть получена позже с помощью Timer.getInfo.

Следующий метод тайм-аута использует @Schedule для установки таймера, который срабатывает каждое воскресенье в полночь:

@Schedule(dayOfWeek="Sun", hour="0")
public void cleanupWeekData() { ... }

Аннотация @Schedules используется для указания нескольких календарных выражений таймера для данного метода тайм-аута.

Следующий метод тайм-аута использует аннотацию @Schedules для установки нескольких календарных выражений таймера. Первое выражение устанавливает таймер, который срабатывает в последний день каждого месяца. Второе выражение устанавливает таймер, который срабатывает каждую пятницу в 11:00 вечера.

@Schedules ({
    @Schedule(dayOfMonth="Last"),
    @Schedule(dayOfWeek="Fri", hour="23")
})
public void doPeriodicCleanup() { ... }

Отмена и сохранение таймеров

Таймеры могут быть отменены следующими событиями.

  • Когда срабатывает таймер одного события, контейнер EJB вызывает соответствующий метод тайм-аута, а затем отменяет таймер.

  • Когда компонент вызывает метод cancel интерфейса Timer, контейнер отменяет таймер.

Если метод вызывается в отменённом таймере, контейнер выбрасывает javax.ejb.NoSuchObjectLocalException.

Чтобы сохранить объект Timer для использования в будущем, вызовите его метод getHandle и сохраните объект TimerHandle в базе данных. (Объект TimerHandle является сериализуемым.) Чтобы восстановить объект Timer, извлеките дескриптор из базы данных и вызовите getTimer для дескриптора. Объект TimerHandle не может быть передан в качестве аргумента метода, определённого в интерфейсе удалённой или веб-сервиса. Другими словами, удалённые клиенты и клиенты веб-сервисов не могут получить доступ к объекту TimerHandle компонента. Локальные клиенты, однако, не имеют этого ограничения.

Получение информации о таймере

В дополнение к определению методов cancel и getHandle интерфейс Timer определяет методы для получения информации о таймерах:

public long getTimeRemaining();
public java.util.Date getNextTimeout();
public java.io.Serializable getInfo();

Метод getInfo возвращает объект, который был последним параметром вызова createTimer. Например, во фрагменте кода createTimer предыдущего раздела этот информационный параметр является объектом String со значением created timer.

Чтобы получить все активные таймеры компонента, вызовите метод getTimers интерфейса TimerService. Метод getTimers возвращает коллекцию объектов Timer.

Транзакции и таймеры

Enterprise-бин обычно создаёт таймер в транзакции. Если эта транзакция откатывается, создание таймера также откатывается. Точно так же, если бин отменяет таймер в транзакции, которая откатывается, отмена таймера откатывается. В этом случае продолжительность таймера сбрасывается, как если бы отмена никогда не происходила.

В бинах, использующих транзакции, управляемые контейнером, метод @Timeout обычно имеет атрибут транзакции Required или RequiresNew, чтобы сохранить целостность транзакции. С этими атрибутами контейнер EJB начинает новую транзакцию перед вызовом метода @Timeout. Если транзакция откатывается, контейнер будет вызывать метод @Timeout как минимум ещё один раз.

Пример timersession

Исходный код для этого примера находится в каталоге tut-install/examples/ejb/timersession/src/main/java/.

TimerSessionBean — это сессионный компонент-синглтон, который показывает, как установить автоматический таймер и программный таймер. В следующем листинге исходного кода TimerSessionBean методы setTimer и @Timeout используются для установки программного таймера. Объект TimerService инъецируется контейнером при создании компонента. Поскольку это бизнес-метод, setTimer доступен для локального представления без интерфейса TimerSessionBean и может вызываться клиентом. В этом примере клиент вызывает setTimer с интервалом в 8000 миллисекунд или 8 секунд. Метод setTimer создаёт новый таймер, вызывая метод createTimer для TimerService. Теперь, когда таймер установлен, контейнер EJB вызовет метод programmaticTimeout для TimerSessionBean, когда наступит время срабатывания таймера, примерно через 8 секунд:

...
    public void setTimer(long intervalDuration) {
        logger.log(Level.INFO,
                "Setting a programmatic timeout for {0} milliseconds from now.",
                intervalDuration);
        Timer timer = timerService.createTimer(intervalDuration,
                "Created new programmatic timer");
    }

    @Timeout
    public void programmaticTimeout(Timer timer) {
        this.setLastProgrammaticTimeout(new Date());
        logger.info("Programmatic timeout occurred.");
    }
...

TimerSessionBean также имеет автоматический таймер и метод тайм-аута, automaticTimeout. Автоматический таймер срабатывает каждую 1 минуту и ​​устанавливается с помощью календарного выражения таймера в аннотации @Schedule:

...
    @Schedule(minute = "*/1", hour = "*", persistent = false)
    public void automaticTimeout() {
        this.setLastAutomaticTimeout(new Date());
        logger.info("Automatic timeout occured");
    }
...

TimerSessionBean также имеет два бизнес-метода: getLastProgrammaticTimeout и getLastAutomaticTimeout. Клиенты вызывают эти методы, чтобы получить дату и время последнего тайм-аута для программного таймера и автоматического таймера соответственно.

Вот исходный код для класса TimerSessionBean:

package javaeetutorial.timersession.ejb;

import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.ejb.Schedule;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerService;

@Singleton
@Startup
public class TimerSessionBean {
    @Resource
    TimerService timerService;

    private Date lastProgrammaticTimeout;
    private Date lastAutomaticTimeout;

    private static final Logger logger =
            Logger.getLogger("timersession.ejb.TimerSessionBean");

    public void setTimer(long intervalDuration) {
        logger.log(Level.INFO,
                "Setting a programmatic timeout for {0} milliseconds from now.",
                intervalDuration);
        Timer timer = timerService.createTimer(intervalDuration,
                "Created new programmatic timer");
    }

    @Timeout
    public void programmaticTimeout(Timer timer) {
        this.setLastProgrammaticTimeout(new Date());
        logger.info("Programmatic timeout occurred.");
    }

    @Schedule(minute = "*/1", hour = "*", persistent = false)
    public void automaticTimeout() {
        this.setLastAutomaticTimeout(new Date());
        logger.info("Automatic timeout occured");
    }

    public String getLastProgrammaticTimeout() {
        if (lastProgrammaticTimeout != null) {
            return lastProgrammaticTimeout.toString();
        } else {
            return "never";
        }
    }

    public void setLastProgrammaticTimeout(Date lastTimeout) {
        this.lastProgrammaticTimeout = lastTimeout;
    }

    public String getLastAutomaticTimeout() {
        if (lastAutomaticTimeout != null) {
            return lastAutomaticTimeout.toString();
        } else {
            return "never";
        }
    }

    public void setLastAutomaticTimeout(Date lastAutomaticTimeout) {
        this.lastAutomaticTimeout = lastAutomaticTimeout;
    }
}

Примечание:

Минимальное время ожидания по умолчанию для сервера GlassFish составляет 1000 миллисекунд или 1 секунда. Если нужно установить значение тайм-аута меньше 1000 миллисекунд, измените значение параметра Minimum Delivery Interval в Консоли администрирования. Чтобы изменить минимальное значение времени ожидания, в Консоли администрирования разверните узел Конфигурации, затем разверните server-config, выберите Контейнер EJB и перейдите на вкладку Сервис таймера EJB. Введите новое значение времени ожидания в поле «Minimum Delivery Interval» и нажмите «Сохранить». Минимальное практическое значение для minimum-delivery-interval-in-millis составляет около 10 миллисекунд из-за ограничений виртуальной машины.

Запуск timersession

Вы можете использовать IDE NetBeans или Maven для сборки, упаковки, развёртывания и запуска примера timersession.

Здесь рассматриваются следующие темы:

Запуск timersession с IDE NetBeans

  1. Удостоверьтесь, чтобы GlassFish Server был запущен (см. Запуск и остановка сервера GlassFish).

  2. В меню «Файл» выберите «Открыть проект».

  3. В диалоговом окне «Открыть проект» перейдите к:

    tut-install/examples/ejb
  4. Выберите каталог timersession.

  5. Нажмите Открыть проект.

  6. В меню «Выполнить» выберите «Выполнить проект».

    Это создаёт и упаковывает приложение в WAR-файл timersession.war, расположенный в каталоге tut-install/examples/ejb/timersession/target/, и развёртывает этот WAR-файл в GlassFish Server, а затем запускает веб-клиент.

Сборка, упаковка и развёртывание timersession с использованием Maven

  1. Удостоверьтесь, чтобы GlassFish Server был запущен (см. Запуск и остановка сервера GlassFish).

  2. В окне терминала перейдите в:

    tut-install/examples/ejb/timersession/
  3. Введите следующую команду:

    mvn install

    Это создаёт и упаковывает приложение в WAR-файл timersession.war, расположенный в каталоге tut-install/examples/ejb/timersession/target/, и развёртывает этот WAR-файл в GlassFish Server.

Запуск веб-клиента

  1. Откройте веб-браузер по следующему URL:

    http://localhost:8080/timersession
  2. Нажмите Set Timer, чтобы установить программный таймер.

  3. Подождите немного и нажмите кнопку «Обновить» в браузере.

    Вы увидите дату и время последних программных и автоматических тайм-аутов.

    Чтобы просмотреть сообщения, которые регистрируются по истечении времени ожидания, откройте файл server.log, расположенный в domain-dir`/logs/`.


Назад Вперёд Содержание
Логотип Oracle  Copyright © 2017, Oracle и/или её дочерних компаний. Все права защищены. Версия перевода 1.0.5 (Java EE Tutorial — русскоязычная версия)