Статьи

Java EE 7: JMS 2.0 со Glassfish v4

Java EE 7 была недавно принята Исполнительным комитетом . Это означает, что скоро у нас должны появиться серверы приложений Java EE 7 на рынке. Одной из спецификаций, составляющих Java EE 7, является JMS 2.0 . Некоторые интересные улучшения были введены с версии 1.1.

В JMS есть много странных вещей, таких как: метод Connection # createSession (boolean transacted, intcknowledgeMode) .

Первый аргумент метода ( transacted ) определяет, должна ли быть сделана сессия. Приложения Java EE имеют JTA, который заботится о транзакциях. Мы можем выбрать, поддерживаются ли транзакции (по умолчанию) или нет. Так зачем нам этот аргумент тогда?

Второй аргумент метода ( acknowledgeMode ) является целочисленной константой, взятой из объекта Session . Серьезно, целочисленные константы делают API, кажется, ооочень устаревшим. Наконец, что означают эти параметры в среде Java EE? JavaDoc говорит, что acceptledgeMode игнорируется, если сеанс выполняется.

Что все это значит? Не что иное, как эти аргументы не имеют никакого смысла для производителя JMS, используемого в контексте Java EE. Вот почему они рекомендуют вам использовать (true, 0) значения параметров, чтобы избежать ненужной путаницы. Это пахнет наследием.

Хорошо, но давайте вернемся к основной теме. Я хотел посмотреть, что нового в мире JMS и как это позволяет мне кодировать проще и удобнее в обслуживании. Я подготовил простое веб-приложение, которое использует JMS 2.0, JAX-RS и EJB (SLSB и MDB), и поместил его в мой репозиторий github.

Предпосылки и инфраструктура

Чтобы запустить этот код, вы должны создать пример очереди. Я настроил его под jms/queue/myqueue JNDI jms/queue/myqueue .

Я использовал Glassfish v4 build 87 . Чтобы использовать API Java EE 7, мне нужно было добавить следующую зависимость Maven:

1
2
3
4
5
6
<dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-api</artifactId>
    <version>7.0-b87</version>
    <scope>provided</scope>
</dependency>

который находится в этом хранилище:

1
2
3
4
<repository>
    <id>Java EE 7</id>
    <url>https://maven.java.net/content/groups/promoted/</url>
</repository>

И это все для конфигурации.

Конфигурация полезной нагрузки и REST

BusinessObject — это простой объект, который будет действовать как полезная нагрузка для наших сообщений JMS. Он будет отправлен производителем и получен потребителем. Ничего особенного, так что давайте двигаться дальше. RESTConfiguration еще проще — он просто определяет префикс конечной точки JAX-RS для нашего приложения. Этот префикс: «/ rest». Вы можете вызывать EJB-компоненты производителя, открывая URL-адрес вашего приложения / rest / seller / jms11 или / rest / seller / jms20 (например, http://localhost:8080/jms2_0_spike/rest/producer/jms20 ).

JMS продюсер

Теперь здесь все становится интересным. Ниже вы можете найти код JMS 1.1 для производителя сообщений SLSB JMS:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Stateless
public class JMS11Producer {
 
    @Resource(lookup = "jms/__defaultConnectionFactory")
    private ConnectionFactory connectionFactory;
 
    @Resource(lookup = "jms/queue/myqueue")
    private Queue queue;
 
    @Path("/jms11")
    @GET
    public String produce() {
        String status = "OK";
 
        Connection connection = null;
        try {
            connection = connectionFactory.createConnection();
            Session session = connection.createSession(true, 0);
            MessageProducer producer = session.createProducer(queue);
 
            BusinessObject payload = new BusinessObject(UUID.randomUUID().toString());
 
            ObjectMessage message = session.createObjectMessage();
            message.setObject(payload);
 
            producer.send(message);
        } catch (JMSException e) {
            status = e.getMessage();
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (JMSException e) {
                    status = e.getMessage();
                }
            }
        }
        return status;
    }
}

Несмотря на путаницу состояния результатов JAX-RS, это многообещающий код, который размывает основную ответственность метода. Он должен просто отправить сообщение в очередь, но он делает очень много вещей. Он создает соединение, сеанс (включая неприятные, игнорируемые параметры), сообщение типа объекта, инициализирует его, а затем, наконец, отправляет сообщение в очередь… о да — конечно, не забывайте о проверенных исключениях и вложенных блоках try / catch. Мы можем попытаться оптимизировать его — например, переместив создание соединения в какой- @PostConstruct метод @PostConstruct и закрыв его на @PreDestroy — но все равно остается много ненужного шума.

Теперь давайте посмотрим на функционально тот же код, выраженный в JMS 2.0:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
@Stateless
public class JMS20Producer {
 
    @Resource(lookup = "jms/queue/myqueue")
    private Queue queue;
 
    @Inject
    private JMSContext jmsContext;
 
    @Path("/jms20")
    @GET
    public String produce() {
        BusinessObject payload = new BusinessObject(UUID.randomUUID().toString());
 
        jmsContext.createProducer().send(queue, payload);
 
        return "OK";
    }
}

Довольно аккуратно, а? Намного легче понять, что делает этот метод: он создает полезную нагрузку и отправляет ее в очередь. Вот и все — вот и весь этот метод. Обработка исключений, создание соединения и сеанса, тип сообщения — все сделано для нас. Если эти обязанности можно перенести в контейнер для облегчения жизни разработчика, то почему бы не сделать это?

Давайте рассмотрим несколько функций JMS 2.0, использованных в этом примере:

  • нет необходимости в ConnectionFactory ,
  • нет необходимости в Connection или Session ,
  • JMSContext — это новый объект, который объединяет возможности Connection и Session ; это может быть введено контейнером,
  • нет проверенных исключений — только JMSRuntimeException ,
  • Цепной вызов для создания сообщений облегчает чтение.

JMS Consumer

Consumer сообщений не сильно изменился со времени JMS 1.1. Это все еще MDB, но теперь легче получить ожидаемую полезную нагрузку из-за приведения сообщений с помощью message.getBody(Clazz) :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@MessageDriven(mappedName = "jms/queue/myqueue")
public class Consumer implements MessageListener {
 
    @Override
    public void onMessage(Message message) {
        try {
            // In JMS 1.1:
            //          ObjectMessage objectMessage = (ObjectMessage)message;
            //          BusinessObject payload = (BusinessObject)objectMessage.getObject();
            BusinessObject payload = message.getBody(BusinessObject.class);
            System.out.println("Message received: " + payload);
        } catch (JMSException e) {
            System.err.println("Error while fetching message payload: " + e.getMessage());
        }
    }
}

Вывод

Это был просто очень быстрый взгляд на JMS 2.0. Однако мне было интересно посмотреть, сколько чистого кода можно создать по сравнению с JMS 1.1. Более подробную информацию о JMS 2.0 смотрите в его официальной спецификации .