JCA 1.5: Часть 2. Управление работой и внедрение транзакций

Синхронное, асинхронное, периодическое и транзакционное выполнение работы

Дэвид Карри продолжает серию из трех статей, посвященных улучшениям и изменениям в последней версии Java™ 2 Enterprise Edition (J2EE) Connector Architecture (JCA). В данной статье он представляет новое соглашение JCA 1.5 по управлению работой, которое позволяет адаптеру ресурсов использовать возможности сервера приложений для планирования и выполнения работы. С помощью другого улучшения JCA, поддержки внедрения транзакций, корпоративная информационная система может выполнить эту работу в своей собственной транзакции.

Дэвид Карри, штатный инженер-программист, IBM UK Ltd

Дэвид Карри (David Currie) недавно пришел в IBM Software Services for WebSphere, базирующуюся в Hursley, Великобритания. До этого он занимался разработкой WebSphere Application Server, сначала транзакциями, а затем проектированием и реализацией JCA-адаптеров ресурсов для поддержки систем обмена сообщениями. Связаться с ним можно по адресу david_currie@uk.ibm.com.



27.02.2008

JCA 1.5, самая последняя версия J2EE Connector Architecture, имеет много важных улучшений и несколько крупных дополнений. Данная статья, вторая в серии из трех статей, описывающих эти изменения, посвящена представленным в первой части функциональным возможностям по управлению работой. В ней рассматривается новое соглашение JCA по управлению работой. Это соглашение позволяет адаптеру ресурсов создавать таймеры для отложенного или периодического выполнения работы и выполнять обработку (либо синхронно, либо асинхронно) с использованием потоков (threads) сервера приложений. В данной статье также рассматривается вопрос, как поддержка внедрения транзакций позволяет выполнить эту обработку в транзакции, которую адаптер ресурсов импортирует на сервер, и как этот адаптер ресурсов может затем управлять завершением транзакции.

Данная серия имеет большое значение для тех, кто хочет использовать эту функциональность в существующем адаптере ресурсов или думает над написанием нового JCA 1.5-совместимого адаптера ресурсов. Также она должна быть интересна тем, кто пишет приложения, использующие адаптеры ресурсов, и хочет знать немного больше о том, что происходит за кулисами.

Выполнение работы

В первой части данной серии статей был представлен интерфейс ResourceAdapter, как механизм для организации собственного цикла жизни адаптера ресурсов на сервере приложений. Возможно, вы помните, что объект BootstrapContext передавался в метод start. Мы тогда не все рассказали об этом объекте. Три метода интерфейса BootstrapContext, приведенные в листинге 1, являются ключевыми для соглашений по управлению работой и внедрению транзакции.

Листинг 1. Интерфейс BootstrapContext, используемый для получения трех вспомогательных объектов
public interface BootstrapContext {

    WorkManager getWorkManager();
    XATerminator getXATerminator();
    Timer createTimer() throws UnavailableException;

}

WorkManager позволяет адаптеру ресурсов планировать работу в синхронном или асинхронном режиме в потоке сервера приложений. Эта работа может быть выполнена в транзакции, импортируемой адаптером ресурсов, и в данном случае завершить работу позволяет XATerminator. Timer обеспечивает возможность отложенного или периодического выполнения работы. В данной статье мы рассмотрим каждый из этих классов подробно и опишем, как их можно использовать.

Интерфейс WorkManager предоставляет три набора методов (doWork, startWork и scheduleWork) для выполнения работы, что показано в листинге 2.

Листинг 2. Интерфейс WorkManager для представления элементов work
public interface WorkManager {
    
    void doWork(Work work) throws WorkException;
    void doWork(Work work, long startTimeout, ExecutionContext execContext,
            WorkListener workListener) throws WorkException;

    long startWork(Work work) throws WorkException;
    long startWork(Work work, long startTimeout, ExecutionContext execContext,
            WorkListener workListener) throws WorkException;

    void scheduleWork(Work work) throws WorkException;
    void scheduleWork(Work work, long startTimeout,
            ExecutionContext execContext, WorkListener workListener)
            throws WorkException;
    
}

Каждый из этих методов принимает в качестве первого параметра экземпляр объекта, реализующего интерфейс Work, показанный в листинге 3.

Листинг 3. Интерфейс Work, реализуемый адаптером ресурсов
public interface Work extends Runnable {

    void release();
    
}

Интерфейс Work расширяет Runnable, и вы должны реализовать элемент work, который будет выполняться в методе run, так же, как это делается при непосредственном программировании Java-потоков. Скоро вы увидите, где начинает работать метод release.

Методы doWork в WorkManager позволяют части работы выполняться синхронно, блокируя процесс до завершения работы. Это может показаться не особенно полезным - разве это не то же самое, что вызов метода run напрямую? Не совсем. Во-первых, вышеуказанные методы позволяют уведомить сервер приложений и том, что сейчас не подходящее время для выполнения работы. Например, если вызывается doWork в области видимости метода ResourceAdapterstart, вы можете обнаружить, что генерируется исключительная ситуация WorkRejectedException, которая уведомляет вас о необходимости как можно более быстрого возврата из этого метода, при необходимости запланировав работу на асинхронный режим.

Во-вторых, если сервер приложений очень занят, он может отложить начало этой части работы. Можно использовать второй параметр startTimeout для указания того, сколько времени адаптер ресурсов готов ожидать начала работы. Если сервер приложений не запустил работу в течение этого периода времени, генерируется исключительная ситуация WorkRejectedException. Интерфейс WorkManager определяет константы IMMEDIATE и INDEFINITE, позволяющие адаптеру ресурсов уведомить о том, что он совсем не готов ждать, или готов ждать бесконечно.

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

Наконец, использование метода doWork обеспечивает для сервера приложений более высокий уровень управления работой, выполняемой адаптером ресурсов. Если адаптер ресурсов находится в середине длинной сложной операции во время остановки сервера приложений, сервер не должен ждать, пока адаптер ресурсов завершит работу или отключится от него. Вместо этого сервер может уведомить адаптер ресурсов, вызвав метод release объекта Work. Адаптер ресурсов должен после этого завершить работу как можно быстрее. В листинге 4 приведен пример возможного использования метода release.

Листинг 4. Пример объекта Work
public class ExampleWork implements Work {

    private volatile boolean _released;

    void run() {
        for (int i = 0; i < 100; i++) {
            if (_released) break;
            // Что-то сделать
        }
    }

    void release() {
        _released = true;
    }
    
}

Обратите внимание на применение ключевого слова volatile в листинге 4. Метод release будет вызываться в отдельном потоке во время работы метода run. Модификатор volatile гарантирует, что метод run будет видеть обновленное поле.

Метод doWork может также завершиться аварийно с довольно странно названной исключительной ситуацией WorkCompletedException. Она генерируется тогда, когда метод run распределяется потоком, но неудачно выполняется настройка контекста или осуществляется выход из метода путем генерирования исключительной ситуации времени исполнения. Предоставляется код ошибки для указания, какой из этих вариантов произошел, и причина проблемы предоставляется в виде ссылки на исключительную ситуацию.


Зачем ждать?

Вы увидели, как методы doWork позволяют выполнить работу, когда вызывающий поток заблокирован. Но что если вы не хотите ждать - то есть, что если вы не хотите синхронного выполнения работы? В среде J2SE (Java 2 Platform, Standard Edition) для достижения этой цели вы организовали бы многопотоковый режим работы. Однако спецификация J2EE разрешает серверу приложений использовать менеджер системы защиты Java 2 для предотвращения выхода приложений из своих потоков. Это разумно, учитывая тот факт, что часть функций сервера приложений должна обеспечивать возможность одновременной работы через пулы EJB-компонентов и сервлетов. Также серверу может понадобиться ассоциировать с потоком различные формы контекста, которые могли бы потеряться при распределении нового потока. Наконец, как я уже упоминал, потоки, которые не контролируются сервером, затрудняют нормальное завершение работы системы.

Хотя зачастую вы можете преодолеть это ограничение путем использования политик безопасности для каждого отдельного случая, методы startWork и scheduleWork объекта WorkManager позволяют адаптеру ресурсов работать в асинхронном режиме, держа сервер приложений под контролем. Метод startWork ожидает начала выполнения части работы, но не ожидает ее завершения. Следовательно, этот метод может использоваться тогда, когда вызывающая сторона должна знать о том, что работа будет выполнена, и ей не нужно ждать ее завершения. Напротив, метод scheduleWork возвращает управление, как только работа принимается к выполнению. В данном случае нет никаких гарантий, что она будет выполнена на самом деле.

Именно в этих асинхронных методах четвертый параметр методов WorkManager в листинге 2 (WorkListener) наиболее полезен. В листинге 5 приведен данный интерфейс.

Листинг 5. Интерфейс WorkListener для получения уведомлений о событиях
public interface WorkListener {

    void workAccepted(WorkEvent e);
    void workRejected(WorkEvent e);
    void workStarted(WorkEvent e);
    void workCompleted(WorkEvent e);
    
}

Адаптер ресурсов может при желании передать объект-прослушиватель, который будет уведомляться о прохождении элементом работы этапов принятия к выполнению (accepted), начала (started) и завершения (completed), а также, в случае сбоя, отмены (rejected). Прослушиватель мог бы использоваться для обратного предоставления уведомления инициатору части работы о ее завершении или о повторном ее планировании в случае какого-либо сбоя. Также имеется класс WorkAdapter, предоставляющий реализации по умолчанию всех методов, для того чтобы подкласс мог переопределить только те, которые нужны в конкретном случае.

Каждый из методов WorkListener принимает в качестве параметра объект WorkEvent, показанный в листинге 6.

Листинг 6. Дополнительные методы класса WorkEvent
public class WorkEvent extends EventObject {

    ...

    public int getType() { ... }
    public Work getWork() { ... }
    public long getStartDuration() { ... }
    public WorkException getException() { ... }
 
}

Кроме обычных методов event класс WorkEvent предоставляет методы accessors (методы доступа) для типа события (accepted, rejected, started или completed) и элемента работы. Это позволяет использовать один (многопоточный) прослушиватель для нескольких заявок на выполнение работы. Имеются также методы для возврата времени, затраченного на начало выполнения работы, и, для workRejected или workCompleted, возврата всех соответствующих исключительных ситуаций WorkRejectedException или WorkCompletedException, которые могут возникнуть.

На рисунке 1 показаны состояния, через которые проходит объект Work.

Рисунок 1. Диаграмма состояний для объекта Work
Рисунок 1. Диаграмма состояний для объекта Work

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


Зачем делать сегодня то, что можно отложить на завтра?

Методы doWork, startWork и scheduleWork немедленно делают объекту WorkManager заявку на выполнение работы. Задержка может возникнуть перед приемом этой заявки объектом WorkManager, но вызывающая сторона может контролировать только максимальную задержку (используя таймаут start). Что если вы хотите, чтобы работа была выполнена позже, а не прямо сейчас? Метод scheduleWork позволяет запланировать работу лишь на другой поток, а не на какое-либо время в будущем.

Здесь на выручку приходит третий метод интерфейса BootstrapContext. Метод createTimer позволяет адаптеру ресурсов получить экземпляр класса java.util.Timer. Этот класс, показанный в листинге 7, является частью стандартных Java-библиотек, начиная с версии 1.3.

Листинг 7. Методы класса Timer
public class Timer  {
 
    public void schedule(TimerTask task, long delay) { ... }
    public void schedule(TimerTask task, Date time) { ... }

    public void schedule(TimerTask task, Date firstTime, long period) { ... }
    public void schedule(TimerTask task, long delay, long period) { ... }
    public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) { ... }
    public void scheduleAtFixedRate(TimerTask task, long delay, long period) { ... }

    public void cancel() { ... }
 
}

Первые два метода класса java.util.Timer можно использовать для планирования выполнения задачи на время сразу после определенной задержки или на указанную дату и время. Остальные четыре метода планирования имеют дополнительный параметр period. Эти методы можно использовать для планирования событий, которые должны возникать периодически после первоначального запуска с интервалом, указанным в параметре period. Методы schedule и scheduleAtFixedRate отличаются между собой, поскольку временные интервалы этих операций не гарантируются; такие операции как сборка мусора (garbage collection) могут привести к более позднему выполнению задания. Если задание задержано, методы schedule все равно будут ожидать полный период времени до выполнения следующего задания. Однако методы scheduleAtFixedRate запускают следующее задание через фиксированный период времени после того, как должно было запуститься предыдущее задание. То есть, если время между заданиями для вас важно, используйте schedule. Если важно абсолютное или общее время, используйте scheduleAtFixedRate.

Каждый из методов schedule в качестве первого параметра принимает объект, расширяющий класс TimerTask. Как и интерфейс Work, этот класс расширяет Runnable, а Timer активизирует метод run в нужное время. Класс TimerTask имеет метод cancel, который можно использовать для отмены последовательных активизаций задания. В качестве альтернативного способа можно вызвать метод cancel с Timer для отмены всех запланированных на данный момент времени заданий. Метод scheduledExecutionTime позволяет методу run сравнить реальное текущее время со временем, когда он должен был быть вызван.

Хотя метод TimerTaskrun активизируется в новом потоке, этот поток распределяется JVM и не управляется сервером приложений. Если адаптер ресурсов должен выполнять какую-либо серьезную обработку, он должен использовать WorkManager для переключения в поток сервера приложений. В листинге 8 приведен пример использования этой хорошей методики для планирования части работы на ежеминутное выполнение, начиная с текущего момента.

Листинг 8. Комбинирование использования Timer и WorkManager
final WorkManager workManager = context.getWorkManager();
final Timer timer = context.createTimer();

timer.scheduleAtFixedRate(new TimerTask() {
    public void run() {
        workManager.scheduleWork(new ExampleWork());
    }
}, 0, 60 * 1000);

Альтернативы JCA WorkManager и Timer

Как упоминалось в предыдущем разделе, существует проблема с использованием Timer в JCA: поток, в котором выполняется TimerTask, не находится под управлением сервера приложений. Поскольку Timer - это класс, а не интерфейс, и потоки на самом деле распределяются в конструкторах этого класса, сервер приложения не может изменить это поведение. Альтернативным вариантом, позволяющим вмешательство сервера приложений, является использование спецификации Timer and Work Manager for Application Servers (см. раздел "Ресурсы"), разработанной совместно IBM и BEA.

При использовании интерфейсов данной спецификации реализация TimerManager получается из пространства имен JNDI (Java Naming and Directory Interface). Реализация TimerListener планируется менеджером взамен Timer (commonj.timers.Timer вместо java.util.Timer). TimerListener активизируется в запланированные моменты времени, а Timer можно использовать для выполнения таких операций как отмена запланированных операций.

Как следует из названия, эта спецификация предоставляет также альтернативу управления работой JCA. Ее интерфейсы очень похожи на интерфейсы для JCA, за исключением того, что первоначальный commonj.work.WorkManager получается из JNDI. Дополнительное поведение, обеспечиваемое данным соглашением, включает способность блокировки до тех пор, пока одна или все части запланированной работы не будут завершены, и возможность выполнения сериализуемых частей работ на удаленной JVM. Как и для commonj Timer, использование commonj WorkManager не ограничивается адаптерами ресурсов. Любой J2EE-компонент на стороне сервера может использовать эту функциональность.

В листинге 9 показано, как выглядит пример из листинга 8, переписанный с использованием классов commonj.

Листинг 9. Использование интерфейсов commonj
final InitialContext context = new InitialContext();
final WorkManager workManager = (WorkManager) 
        context.lookup("java:comp/env/wm/MyWorkManager");
final TimerManager timerManager = (TimerManager)
	context.lookup("java:comp/env/timer/MyTimer");

timerManager.scheduleAtFixedRate(new TimerListener() {
    public void timerExpired(final Timer timer) {
        try {
            workManager.schedule(new ExampleCommonjWork());
        } catch (final WorkException exception) {
        }
    }
}, 0, 60 * 1000);

Класс ExampleCommonjWork идентичен классу ExampleWork из листинга 4 за исключением того, что он реализует дополнительный метод isDaemon, требуемый интерфейсом commonj Work. Этот метод должен возвращать true, если работа является долговременной.


Импорт транзакций и их завершение

До JCA 1.5 сервер приложений всегда выступал в качестве координатора транзакций. Сервер приложений отвечает за начало транзакции и, после зачисления каждым менеджером ресурсов своих XAResource посредством менеджера JCA-соединений, за координацию завершения транзакции путем фиксации или отката всех ресурсов. Соглашение JCA 1.5 по внедрению транзакций позволяет корпоративным информационным системам (Enterprise Information System, EIS) выступать в роли координатора транзакций, начиная и завершая транзакцию. EIS может затем импортировать транзакцию в сервер приложений при помощи адаптера ресурсов и выполнять работу на сервере в рамках этой транзакции. Например, можно активизировать управляемый сообщениями компонент (Message-Driven Bean, MDB) с атрибутом транзакций менеджера контейнеров, установленным в значение Supports. После этого работа, выполняемая методом MDB, будет частью данной транзакции, включая вызовы к другим EJB-компонентам.

Адаптер ресурсов импортирует транзакцию через третий параметр ExecutionContext, который мы передавали в WorkManager в листинге 2. Как видно из листинга 10, этот класс имеет методы для установки идентификатора транзакции (XID) и значения таймаута транзакции.

Листинг 10. Класс ExecutionContext, передаваемый в WorkManager
public class ExecutionContext {

    public ExecutionContext() { ... }
    public void setXid(Xid xid) { ... }
    public Xid getXid() { ... }
    public void setTransactionTimeout(long timeout)
        throws NotSupportedException { ... }
    public long getTransactionTimeout() { ... }    
    
}

XID уникальным образом идентифицирует транзакцию и состоит из трех частей: идентификатора формата, классификатора транзакции (transaction qualifier) и классификатора семейства (branch qualifier). Если EIS еще не имеет XID для транзакции, он должен быть создан в соответствии со спецификацией XA (см. раздел "Ресурсы"). Затем сервер приложений ассоциирует эту транзакцию с потоком выполнения до вызова метода run объекта work.

Импортировав транзакцию в сервер приложений, адаптер ресурсов начинает отвечать за уведомление сервера о событиях, относящихся к этой транзакции. В частности, он должен уведомлять сервер приложений о завершении транзакции. Это делается через интерфейс XATerminator, показанный в листинге 11, реализация которого может быть получена из BootstrapContext.

Листинг 11. Класс XATerminator, использующийся для завершения транзакции
public interface XATerminator {

    void commit(Xid xid, boolean onePhase) throws XAException;
    void forget(Xid xid) throws XAException;
    int prepare(Xid xid) throws XAException;
    Xid[] recover(int flag) throws XAException;
    void rollback(Xid xid) throws XAException;
    
}

Методы интерфейса XATerminator соответствуют методам интерфейса XAResource, только в этом случае адаптер ресурсов активизирует сервер приложений. Обычно, если в транзакцию включено более одного ресурса, адаптер ресурсов вызовет prepare в XATerminator, передавая тот же Xid, который был получен в ExecutionContext. При возврате всеми ресурсами значения XA_OK (или XA_RDONLY), адаптер ресурсов вызовет commit, в противном случае - rollback.


Заключение

В данной статье было рассмотрено применение интерфейса WorkManager для планирования выполнения работы в потоке под управлением сервера приложений в синхронном и асинхронном режиме. Вы увидели, как использовать экземпляры Timer для выполнения работы в будущем или периодически, узнали об альтернативных (commonj) способах использования объектов, предоставляемых JCA. Вы увидели, как адаптер ресурсов может выполнить работу в транзакции, которую импортировал на сервер приложений, а затем использовать интерфейс XATerminator для управления завершением транзакции. В третьей и последней части данной серии статей я рассмотрю соглашение JCA 1.5 по внедрению сообщений, больше известному под названием поддержка управляемых сообщениями компонентов (message-driven bean support).

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=291929
ArticleTitle=JCA 1.5: Часть 2. Управление работой и внедрение транзакций
publish-date=02272008