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

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

Использование JMS API в приложениях Java EE

В этом разделе описывается, как использование JMS API в корпоративных приложениях или веб-приложениях отличается от использования его в клиентских приложениях.

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

Обзор использования JMS API

Общее правило в спецификации платформы Java EE применяется ко всем компонентам Java EE, которые используют JMS API в EJB или веб-контейнерах: прикладные компоненты в веб- и EJB-контейнерах не должны пытаться создавать более одного активного (не закрытого) объекта Session для каждого соединения. Однако допускается использование нескольких объектов JMSContext, поскольку они объединяют одно соединение и одну сессию.

Это правило не распространяется на клиентские приложения. Клиентский контейнер приложения поддерживает создание нескольких сессий для каждого соединения.

Создание ресурсов для приложений Java EE

Можно использовать аннотации для создания специфичных для приложения фабрик соединений и пунктов назначения для Enterprise-бина Java EE или веб-компонентов. Ресурсы, созданые таким образом, видны только приложению, для которого их создают.

Можно использовать элементы дескриптора развёртывания для создания этих ресурсов. Элементы, указанные в дескрипторе развёртывания, переопределяют элементы, указанные в аннотациях. Смотрите Упаковка приложений для получения базовой информации о дескрипторах развёртывания. Для создания специфичных для приложения ресурсов для клиентских приложений нужно использовать дескриптор развёртывания.

Чтобы создать пункт назначения, используйте аннотацию @JMSDestinationDefinition, как показано ниже:

@JMSDestinationDefinition(
    name = "java:app/jms/myappTopic",
    interfaceName = "javax.jms.Topic",
    destinationName = "MyPhysicalAppTopic"
  )

Элементы name, interfaceName и destinationName обязательны. Указание элемента description опционально. Чтобы создать несколько пунктов назначения, заключите их в аннотацию @JMSDestinationDefinitions, разделив запятыми.

Чтобы создать фабрику соединений, используйте аннотацию @JMSConnectionFactoryDefinition, как показано ниже для класса:

@JMSConnectionFactoryDefinition(
    name="java:app/jms/MyConnectionFactory"
)

Элемент name обязателен. При желании могут быть указаны ряд других элементов, таких как clientId, если будет использоваться фабрика соединений для долговременных подписок, или description. Если не указан interfaceName, по умолчанию используется интерфейс javax.jms.ConnectionFactory. Чтобы создать несколько фабрик соединений, заключите их в аннотацию @JMSConnectionFactoryDefinitions, разделив запятыми.

Аннотация должна быть указана только один раз для данного приложения, в любом из компонентов.

Замечание:

Если приложение содержит один или несколько компонентов, управляемых сообщениями, вы можете разместить аннотацию в одном из компонентов, управляемых сообщениями. Если аннотация размещена в отправляющем компоненте, таком как клиентское приложение, должен быть указан элемент mappedName для поиска темы вместо использования свойства destinationLookup объекта конфигурации активации.

Когда инъецируется ресурс в компонент, нужно использовать значение элемента name в аннотации определения в качестве значения элемента lookup аннотации @Resource:

@Resource(lookup = "java:app/jms/myappTopic")
private Topic topic;

Доступны следующие переносимые пространства имён JNDI. Какие из них использовать, зависит способа упаковки приложения.

  • java:global: делает ресурс доступным для всех развёрнутых приложений

  • java:app: делает ресурс доступным для всех компонентов во всех модулях в одном приложении

  • java:module: делает ресурс доступным для всех компонентов в данном модуле (например, для всех Enterprise-бинов в модуле EJB)

  • java:comp: делает ресурс доступным только для одного компонента (кроме веб-приложения, где он эквивалентен java:module)

См. документацию API о деталях этих аннотаций. Все примеры в Отправка и получение сообщений с использованием простого веб-приложения, Отправка сообщений из сессионного компонента в MDB и Использование сущности для объединения сообщений из двух MDB используют аннотацию @JMSDestinationDefinition. Другие примеры JMS не используют эти аннотации. Примеры, состоящие только из клиентских приложений, не развёртываются на сервере приложений и поэтому должны обмениваться данными друг с другом с помощью административно созданных ресурсов, которые существуют за пределами отдельных приложений.

Использование инъецирования ресурсов в Enterprise-бине и веб-компонентах

Инъецирование ресурсов может использоваться в приложениях Java EE для инъецирования как администрируемых объектов, так и объектов JMSContext.

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

Инъецирование фабрики соединений, очереди и темы

Обычно для инъецирования ConnectionFactory, Queue или Topic в приложение Java EE используется аннотация @Resource. Эти объекты должны быть созданы административно перед развёртыванием приложения. Возможно, вы захотите использовать фабрику соединений по умолчанию с именем JNDI java:comp/DefaultJMSConnectionFactory.

Когда используется инъецирование ресурсов в клиентском компоненте приложения, ресурс JMS обычно объявляется статически:

@Resource(lookup = "java:comp/DefaultJMSConnectionFactory")
private static ConnectionFactory connectionFactory;

@Resource(lookup = "jms/MyQueue")
private static Queue queue;

Однако, при использовании этой аннотации в сессионном компоненте, управляемом сообщениями компоненте или веб-компоненте, ресурс не должен объявляться статически:

@Resource(lookup = "java:comp/DefaultJMSConnectionFactory")
private ConnectionFactory connectionFactory;

@Resource(lookup = "jms/MyTopic")
private Topic topic;

Объявление ресурса статическим в этих компонентах приведёт к ошибкам во время выполнения.

Инъецирование объекта JMSContext

Чтобы получить доступ к объекту JMSContext в Enterprise-бине или веб-компоненте, вместо инъецирования ресурса ConnectionFactory и создания JMSContext могут использоваться аннотации @Inject и @JMSConnectionFactory для инъецирования JMSContext. Чтобы использовать фабрику соединений по умолчанию, используется код, подобный следующему:

@Inject
private JMSContext context1;

Чтобы использовать собственную фабрику соединений, используется код, подобный следующему:

@Inject
@JMSConnectionFactory("jms/MyConnectionFactory")
private JMSContext context2;

Использование компонентов Java EE для отправки и синхронного получения сообщений

Приложение, создающее сообщения или синхронно принимающее их, может использовать для выполнения этих операций веб-компонент Java EE или компонент EJB, такой как Managed-бин, сервлет или сессионный компонент. В примере Отправка сообщений из сессионного компонента в MDB используется сессионный компонент без сохранения состояния для отправки сообщений в тему. В примере Отправка и получение сообщений с использованием простого веб-приложения используются Managed-бины для отправки и получения сообщений.

Поскольку синхронное получение без заданного тайм-аута связывает ресурсы сервера, этот механизм сложно назвать хорошим дизайном приложения в случае веб-компонента или компонента EJB. Вместо этого используется синхронное получение с указанием тайм-аута или управляемый сообщениями компонент с асинхронным получением сообщений. Для подробной информации о синхронном получении см. Потребители сообщений JMS.

Использование JMS API в компоненте Java EE во многом похоже на использование его в клиентском приложении. Основными отличиями являются области управления ресурсами и транзакции.

Управление JMS-ресурсами в Web и EJB-компонентах

Ресурсы JMS — это соединение и сессия, которые обычно объединяются в объект JMSContext. В общем, важно освобождать ресурсы JMS, когда они больше не используются. Вот несколько полезных практик.

  • Если ресурс JMS поддерживается только в течение жизненного цикла бизнес-метода, используйте оператор try-with-resources для создания JMSContext, чтобы он был закрыт автоматически в конце блока try.

  • Чтобы поддерживать ресурс JMS в течение транзакции или запроса, инъецируйте JMSContext как описано в Инъецирование объекта JMSContext. Это также приведёт к освобождению ресурса, когда он больше не нужен.

  • Чтобы сохранить ресурс JMS для жизненного цикла объекта Enterprise-бина, можно использовать Callback-метод @PostConstruct для создания ресурса и Callback-метод @PreDestroy для закрытия ресурса. Однако обычно в этом нет необходимости, поскольку серверы приложений, как правило, поддерживают пул соединений. Если используется сессионный компонент с сохранением состояния и ресурс JMS сохраняется в кэшированном состоянии, нужно закрыть ресурс в Callback-методе @PrePassivate и установить его значение в null. В Callback-методе @PostActivate его требуется создавать снова.

Управление транзакциями в сессионных бинах

Вместо локальных транзакций используются транзакции JTA. Можно использовать как транзакции, управляемые контейнером, так и транзакции, управляемые компонентом. Обычно используются управляемые контейнером транзакции для методов бинов, которые выполняют отправку или получение, что позволяет контейнеру EJB обрабатывать разграничение транзакций. Поскольку по умолчанию используются управляемые контейнером транзакции, указывать их не нужно.

Можно использовать управляемые компонентом транзакции и методы разграничения транзакций интерфейса javax.transaction.UserTransaction, но делать это следует только в том случае, если у приложения есть особые требования и вы являетесь экспертом в использовании транзакций. Обычно управляемые контейнером транзакции реализуют наиболее эффективное и правильное поведение. В этом руководстве нет примеров транзакций, управляемых компонентом.

Использование управляемых сообщениями бинов для асинхронного получения сообщений

В разделах Что такое бин, управляемый сообщениями? и Как JMS API работает с платформой Java EE? описывается, как платформа Java EE поддерживает особый тип Enterprise-бина — компонент, управляемый сообщениями, который позволяет приложениям Java EE асинхронно обрабатывать сообщения JMS. Другие веб- и EJB-компоненты Java EE позволяют отправлять сообщения и получать их синхронно, но не асинхронно.

Управляемый сообщениями компонент — это слушатель сообщений, которому сообщения могут быть доставлены из очереди или из темы. Сообщения могут отправляться любым компонентом Java EE (из клиентского приложения, другого Enterprise-бина или веб-компонента) или из приложения или системы, которая не использует технологию Java EE.

К классу бина, управляемого сообщениями, предъявляются следующие требования.

  • Он должен быть аннотирован @MessageDriven, если не использует дескриптор развёртывания.

  • Класс должен быть определён как public, но не как abstract или final.

  • Он должен содержать публичный конструктор без аргументов.

Рекомендуется, но не обязательно, чтобы класс управляемого сообщениями компонента реализовывал интерфейс слушателя сообщений для поддерживаемого им типа сообщения. Компонент, поддерживающий JMS API, реализует интерфейс javax.jms.MessageListener и должен предоставлять метод onMessage со следующей сигнатурой:

void onMessage(Message inMessage)

Метод onMessage вызывается контейнером, когда для компонента поступает сообщение. Этот метод содержит бизнес-логику, которая обрабатывает сообщения. Бин, управляемый сообщениями, ответствен за анализ сообщения и выполнение необходимой бизнес-логики.

Компонент, управляемый сообщениями, отличается от приёмника сообщений клиентского приложения следующими способами.

  • В клиентском приложении необходимо создать JMSContext, затем JMSConsumer, а затем вызвать setMessageListener для активации слушателя. Для бина, управляемого сообщениями, нужно только определить класс и аннотировать его, а контейнер EJB создаст его.

  • Класс бина использует аннотацию @MessageDriven, которая обычно включает элемент activationConfig, содержащий аннотации @ActivationConfigProperty, определяющие свойства, используемые бином или фабрикой соединений. Эти свойства могут включать фабрику соединений, тип пункта назначения, долговременную подписку, селектор сообщений или режим подтверждения. Некоторые из примеров в главе 49 «Примеры JMS» устанавливают эти свойства. Также можно установить свойства в дескрипторе развёртывания.

  • Контейнер клиентского приложения имеет только один объект MessageListener, который вызывается одновременно для одного потока. Однако управляемый сообщениями компонент может иметь несколько объектов, сконфигурированных контейнером, которые могут вызываться одновременно несколькими потоками (хотя каждый объект вызывается только одним потоком одновременно). Поэтому бины, управляемые сообщениями, могут обрабатывать сообщения намного быстрее, чем слушатели сообщений.

  • Не нужно указывать режим подтверждения сообщения, если не используются транзакции, управляемые компонентом. Сообщение будет получено в транзакции, в которой вызывается метод onMessage.

Таблица 48-3 перечисляет свойства конфигурации активации, определенные спецификацией JMS.

Таблица 48-3. Параметры @ActivationConfigProperty для управляемых сообщениями компонентов

Имя свойства

Описание

acknowledgeMode

Режим подтверждения, используемый только для управляемых компонентом транзакций. По умолчанию используется Auto-acknowledge (также разрешено Dups-ok-acknowledge)

destinationLookup

Имя для поиска очереди или темы, из которой бин будет получать сообщения

destinationType

javax.jms.Queue или javax.jms.Topic

subscriptionDurability

Для долговременных подписок нужно установить значение Durable. Смотрите Создание долговременных подписок для получения дополнительной информации

clientId

Для долговременных подписок — идентификатор клиента для подключения (необязательно)

subscriptionName

Для долговременных подписок — название подписки

messageSelector

Строка, фильтрующая сообщения. Смотрите Селекторы сообщений JMS для информации

connectionFactoryLookup

Имя для поиска фабрики соединений, которая будет использоваться для подключения к провайдеру JMS, от которого компонент будет получать сообщения

Вот пример компонента, управляемого сообщениями, используемый в Асинхронный приём сообщений с использованием бина, управляемого сообщениями:

@MessageDriven(activationConfig = {
    @ActivationConfigProperty(propertyName = "destinationLookup",
            propertyValue = "jms/MyQueue"),
    @ActivationConfigProperty(propertyName = "destinationType",
            propertyValue = "javax.jms.Queue")
})
public class SimpleMessageBean implements MessageListener {

    @Resource
    private MessageDrivenContext mdc;
    static final Logger logger = Logger.getLogger("SimpleMessageBean");

    public SimpleMessageBean() {
    }

    @Override
    public void onMessage(Message inMessage) {

        try {
            if (inMessage instanceof TextMessage) {
                logger.log(Level.INFO,
                        "MESSAGE BEAN: Message received: {0}",
                        inMessage.getBody(String.class));
            } else {
                logger.log(Level.WARNING,
                        "Message of wrong type: {0}",
                        inMessage.getClass().getName());
            }
        } catch (JMSException e) {
            logger.log(Level.SEVERE,
                    "SimpleMessageBean.onMessage: JMSException: {0}",
                    e.toString());
            mdc.setRollbackOnly();
        }
    }
}

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

Класс бина обычно инъецирует ресурс MessageDrivenContext, предоставляющий некоторые дополнительные методы, которые можно использовать для управления транзакциями (например,setRollbackOnly):

    @Resource
    private MessageDrivenContext mdc;

Бин, управляемый сообщениями, никогда не имеет локального или удалённого интерфейса. Вместо этого у него есть только класс бина.

Бин, управляемый сообщениями, в некотором смысле похож на сессионный бин без состояния: его объекты относительно недолговечны и не сохраняют состояния для конкретного клиента. Переменные объекта бина, управляемого сообщениями, могут содержать некоторое состояние при обработке клиентских сообщений: например, соединение с открытой базой данных или ссылка на объект Enterprise-бина.

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

Подробнее о жизненном цикле бина, управляемого сообщениями, см. Жизненный цикл бина, управляемого сообщениями.

Управление транзакциями JTA

Клиентские приложения Java EE и клиенты Java SE используют локальные транзакции JMS (описанные в Использование локальных транзакций JMS), которые позволяют группировать отправку и получение в пределах конкретной сессии JMS. Приложения Java EE, которые выполняются в веб-контейнере или контейнере EJB, обычно используют транзакции JTA для обеспечения целостности доступа к внешним ресурсам. Основное различие между транзакцией JTA и локальной транзакцией JMS заключается в том, что транзакция JTA контролируется менеджерами транзакций сервера приложений. Транзакции JTA могут быть распределены, что означает, что они могут охватывать несколько ресурсов в одной транзакции, таких как провайдер JMS и база данных.

Например, распределённые транзакции позволяют нескольким приложениям атомарно выполнять обновления для одной и той же базы данных, а также они позволяют одному приложению атомарно выполнять обновления для нескольких баз данных.

В приложении Java EE, которое использует JMS API, могут использоваться транзакции для объединения отправки или получения сообщений с обновлениями базы данных и другими операциями менеджера ресурсов. Можно получить доступ к ресурсам из нескольких компонентов приложения в рамках одной транзакции. Например, сервлет может запустить транзакцию, получить доступ к нескольким базам данных, вызвать Enterprise-бин, отправляющий сообщение JMS, вызвать другой Enterprise-бин, модифицирующий информационную систему с использованием архитектуры коннекторов, и, наконец, зафиксировать транзакцию. Однако приложение не может отправить сообщение JMS и получить ответ на него в рамках одной транзакции.

Транзакции JTA внутри EJB и веб-контейнеров могут быть двух видов.

  • Управляемые контейнером транзакции: контейнер контролирует целостность транзакций без необходимости вызывать commit или rollback. Управляемые контейнером транзакции проще в использовании, чем управляемые бином транзакции. Можно указать соответствующие атрибуты транзакции для методов Enterprise-бина.

    Используйте атрибут транзакции Required (по умолчанию), чтобы гарантировать, что метод всегда является частью транзакции. Если транзакция выполняется при вызове метода, метод будет частью этой транзакции. Если нет, новая транзакция будет запущена до вызова метода и будет зафиксирована после его возврата. Смотрите Атрибуты транзакции для получения дополнительной информации.

  • Управляемые компонентом транзакции: могут использоваться вместе с интерфейсом javax.transaction.UserTransaction, предоставляющим собственные методы commit и rollback, которые могут использоваться для разграничения транзакций. Управляемые бином транзакции рекомендуются только тем, кто имеет опыт программирования транзакций.

С компонентами, управляемыми сообщениями, могут использоваться транзакции как управляемые контейнером, так и управляемые компонентом. Чтобы обеспечить получение и обработку всех сообщений в контексте транзакции, следует использовать транзакции, управляемые контейнером, и атрибут транзакции Required (по умолчанию) для метода onMessage.

Когда используются транзакции, управляемые контейнером, можно вызывать следующие методы MessageDrivenContext.

  • setRollbackOnly: используйте этот метод для обработки ошибок. Если возникает исключение, setRollbackOnly отмечает текущую транзакцию, так что единственным возможным результатом транзакции является откат.

  • getRollbackOnly: используйте этот метод, чтобы проверить, была ли текущая транзакция помечена для отката.

Если используются транзакции, управляемые бинами, доставка сообщения методу onMessage происходит вне контекста транзакции JTA. Транзакция начинается, когда вызывается метод UserTransaction.begin в методе onMessage, и заканчивается с вызовом UserTransaction.commit или UserTransaction.rollback. Любой вызов метода Connection.createSession должен происходить внутри транзакции.

Использование транзакций, управляемых компонентом, позволяет обрабатывать сообщение с помощью более чем одной транзакции или выполнять некоторые части обработки сообщения вне контекста транзакции. Однако если используются транзакции, управляемые контейнером, сообщение принимается MDB и обрабатывается методом onMessage в той же транзакции. Невозможно добиться такого поведения с помощью управляемых компонентом транзакций.

Когда JMSContext создаётся в транзакции JTA (в веб-контейнере или EJB-контейнере), контейнер игнорирует любые заданные аргументы, поскольку он управляет всеми свойствами транзакции. Когда JMSContext создаётся в веб-контейнере или контейнере EJB и транзакция JTA отсутствует, значение (если оно есть), передаваемое методу createContext, должно быть JMSContext. AUTO_ACKNOWLEDGE или JMSContext.DUPS_OK_ACKNOWLEDGE.

Когда используются управляемые контейнером транзакции, обычно используется атрибут транзакции Required (по умолчанию) для бизнес-методов Enterprise-бина.

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

Если управляемый сообщениями компонент использует транзакции, управляемые компонентом, получение сообщения не может быть частью транзакции, управляемой компонентом. Можно установить для свойства конфигурации активации acknowledgeMode значение Auto-acknowledge или Dups-ok-acknowledge, чтобы указать, как именно сообщение будет подтверждено бином, управляемым сообщениями.

Если метод onMessage выдает RuntimeException, контейнер не подтверждает обработку сообщения. В этом случае провайдер JMS повторно доставит неподтверждённое сообщение в будущем.


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