JMS сообщения в Java приложении

JMS (Java Message Service) является стандартом обмена сообщениями между приложениями. Java приложения, выполненные по технологии Java SE (standalone) или Java EE (WEB) могут создавать, отправлять и получать JMS сообщения. Программное обеспечение, используемое для передачи сообщений между приложениями по стандарту JMS, формирует очереди сообщений queue.

Отправка сообщений java-приложениями выполняется в асинхронном режиме, т.е. процедура не ждет ответа от получателя. В качестве получателей сообщений, организующих очереди (queue), может быть либо программное обеспечение типа WebSphere MQ, связывающее приложения через канал обмена сообщениями, либо WEB-контейнер типа JBoss, GlassFish, обеспечивающих обмен сообщениями между приложениями контейнера, либо по каналам Интернета с использованием JNDI.

Получение сообщений java-приложением осуществляется либо по «подписке» (технология «издатель-подписчик»), либо из очереди («точка-точка»). Для получения сообщений по подписке необходимо должным образом подключиться к соответствующей очереди. При появлении в очереди сообщений они отправляются всем подписчикам. В модели «точка-точка» для получения сообщений необходимо периодически подключаться к соответствующей очереди и читать в ней сообщения.

В статье рассматриваются примеры отправки и получения сообщений с использованием как WEB-приложения (Java EE), так standalone приложения (Java SE). Описание примеров разделено на две части. На этой странице рассматривается взаимодействие WEB-приложения с провайдером HornetQ. Взаимодействие standalone Java приложения с Websphere MQ рассматривается здесь.

Пример обмена сообщения с сервером JBoss

В первом простейшем WEB-приложении «jms-jboss» для отправки и получения JMS сообщений в качестве контейнера будет использован сервер приложений Wildfly версии 8.2 (ранее JBoss Application Server или JBoss AS).

Настройка сервера приложений Wildfly

Чтобы использовать Wildfly для обмена сообщениями JMS необходимо настроить его файл конфигурации {WILDFLY_INSTALL_DIR}/standalone/configuration/standalone.xml. По умолчанию настройки JMS не включены в конфигурационный файл и их нужно определить вручную. Но можно использовать файл конфигурации standalone-full.xml, в котором сервер включает настройки JMS провайдера HornetQ, обеспечивающего создание соответствующих очередей и обмен сообщениями.

В файл конфигурации standalone-full.xml в раздел <hornetq-server> секции <subsystem xmlns="urn:jboss:domain:messaging:2.0"> добавим очередь. Две очереди (ExpiryQueue и DLQ) уже имеются в подразделе <jms-destinations>. Добавим свою очередь testQueue с JNDI 'jms/queue/test' :

<subsystem xmlns="urn:jboss:domain:messaging:2.0">
    <hornetq-server>
    ...
        <jms-destinations>
            <jms-queue name="ExpiryQueue">
                <entry name="java:/jms/queue/ExpiryQueue"/>
            </jms-queue>
            <jms-queue name="DLQ">
                <entry name="java:/jms/queue/DLQ"/>
            </jms-queue>
            <jms-queue name="testQueue">
                <entry name="jms/queue/test"/>
                <entry name="java:jboss/exported/jms/queue/test"/>
            </jms-queue>
        </jms-destinations>
    </hornetq-server>
</subsystem>

Для примера достаточно было добавить один элемент '<entry name="jms/queue/test"/>', который работает внутри контейнера. Второй элемент "java:jboss/exported/jms/queue/test" может работать за пределами контейнера, т.е. из другой JVM. Для него обязательным условием является определение в начале наименования "java:jboss/exported/". Можно было бы конечно использовать и существующие очереди (ExpiryQueue и DLQ).

Примечание :
DLQ (Dead Letter Queue) – это локальная очередь, называемая инача как очередь недоставленных сообщений. Такую очередь создают для каждого менеджера очередей, чтобы отлавливать неотправленные сообщения, связанные с проблемами в сети или у получателя.

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

Чтобы стартовать Wildfly с файлом конфигурации standalone-full.xml из IDE Eclipse необходимо открыть окно 'JBoss Runtime' и определить значение 'Configuration file'. Для этого откройте вкладку Servers (Perspective 'Java EE') и дважды щелкните мышью на сервере Wildfly. В открывшемся окне Overview нажмите на ссылку 'Runtime Enviroment', в результате чего будет открыто окно 'JBoss Runtime' :

Для запуска сервера приложений Wildfly не из IDE Eclipse c конфигурационным файлом standalone-full.xml можно использовать командный файл, в котором определить файл в качестве параметра : ./standalone.sh -c standalone-full.xml

В конце страницы приведен Log сервера Wildfly, в котором показано создание соответствующих очередей и подключение MDB-объекта приложения к адаптеру HornetQ для получения JMS сообщений по подписке.

Описание примера

На следующем скриншоте приведена структура WEB-приложения jms-jboss, включающего :

  • ServiceServlet - сервлет, используемый для отправки и получения JMS сообщений;
  • Sender - отправитель JMS сообщений;
  • Receiver - MDB получатель JMS сообщений;
  • jquery-3.2.1.min.js - библиотека jQuery для асинхронных ajax-вызовов сервлета;
  • index.jsp - интерфейсная страница приложения.

Проект в среде IDE Eclipse с использованием maven представлен на следующем скриншоте.

Листинг сервлета ServiceServlet

Сервлет ServiceServlet вызывается со страницы index.jsp из браузера с использованием jQuery. В качестве параметров сервлет получает 'prefix' сообщения и команду 'mode'. Для отправки JMS сообщения сервлет использует метод sendMessage класса Sender. Отправленные сообщения сервер Wildfly сразу же возвращает подписчику Receiver. Полученные сообщения сервлет читает из статической коллекции Receiver.messages, после чего очищает её.

Отправленные и полученные сообщения сервлет оформляет в виде HTML-кода и возвращает в браузер.

package org.jms.example;

import java.io.IOException;
import java.io.PrintWriter;
 
import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public class ServiceServlet extends HttpServlet
{
    private static final long serialVersionUID = 1L;
 
    @Inject
    Sender sender;

    private final String BR = "<br />";
    @Override
    protected void service(HttpServletRequest request, 
                           HttpServletResponse response)
                    throws ServletException, IOException
    {
        String mode   = request.getParameter("mode"  );
        String prefix = request.getParameter("prefix");

        if(prefix == null || "".equals(prefix))
            prefix = "prefix";

        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        if (mode.equalsIgnoreCase("send")) {
            out.print("send messages :" + BR);
            for (int i = 0; i < 3; i++) {
                String msg = prefix + " #" + i;
                out.print("&bull; " + msg + BR);
                sender.sendMessage(msg);
            }
            out.close();
        } else {
            out.print("receive messages :" + BR);
            if (Receiver.messages.size() > 0) {
                for (int i=0; i<Receiver.messages.size(); i++) {
                    out.print("&mdash; " + 
                       Receiver.messages.get(i) + BR);
                }
                Receiver.messages.clear();
            }
            out.close();
        }
    }
}

Листинг дескриптора приложения, web.xml

В дескрипторе приложения web.xml описывается сервлет ServiceServlet и его URL (url-pattern) для вызова, а также открываемая по умолчанию страница приложения.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xmlns="http://java.sun.com/xml/ns/javaee" 
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
             http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
  <display-name>JMS Application</display-name>
  
  <servlet>
      <servlet-name>JMSServlet</servlet-name>
      <servlet-class>org.jms.example.ServiceServlet</servlet-class>
  </servlet>
  <servlet-mapping>
      <servlet-name>JMSServlet</servlet-name>
      <url-pattern>/service</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
      <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

Листинг класса отправителя сообщений Sender

Класс отправителя создается и «живет» вместе с WEB-приложением согласно аннотации @ApplicationScoped. При инсталляции сразу же определяются context и очередь queue. Сервлет отправляет текстовые сообщения, вызывая метод sendMessage.

package org.jms.example;

import javax.annotation.Resource;

import javax.inject.Inject;

import javax.jms.Queue;
import javax.jms.JMSContext;

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class Sender
{
    @Resource(mappedName = "jms/queue/test")
    private Queue queue;
 
    @Inject
    private JMSContext context;
 
    public void sendMessage(String txt) {
        context.createProducer().send(queue, txt);
    }
}

Листинг получателя сообщений Receiver

Здесь следует несколько слов сказать о MDB (Message Driven Beans). Объект MDB используется для поддержки асинхронных коммуникаций в приложении, как правило, в сочетании с очередями. Клиент посылает сообщение в очередь, а MDB объект получает эти сообщения из очереди по подписке. Клиент не может вызывать MDB напрямую, связь обеспечивается с помощью сообщений JMS. MDB никогда не отвечает клиенту.

Класс получателя сообщений оформлен как MDB объект с использованием аннотации, в которой определен ряд параметров, включающий JNDI очереди 'jms/queue/test'. Получатель реализует интерфейс javax.jms.MessageListener, согласно которому переопределяет метод onMessage.

При появлении в очереди 'jms/queue/test' сообщения сервер приложений Wildfly сразу же вызовет метод onMessage данного MDB-объекта и передаст ему в качестве параметра сообщение javax.jms.Message, текст которого будет сохранен в коллекции messages. Сервлет имеет прямой доступ к набору полученных сообщений messages с модификаторами public и static.

package org.jms.example;

import java.util.List;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
 
import javax.jms.Message;
import javax.jms.JMSException;
import javax.jms.MessageListener;

import javax.ejb.MessageDriven;
import javax.ejb.ActivationConfigProperty;
 
@MessageDriven(name = "Receiver", activationConfig = {
  @ActivationConfigProperty(propertyName = "destinationLookup", 
                            propertyValue = "jms/queue/test"),
  @ActivationConfigProperty(propertyName = "destinationType", 
                            propertyValue = "javax.jms.Queue"),
  @ActivationConfigProperty(propertyName = "acknowledgeMode", 
                            propertyValue = "Auto-acknowledge") })

public class Receiver implements MessageListener
{
    private Logger LOG = Logger.getLogger(Receiver.class.getName());

    public static List<String> messages = new ArrayList<String>();

    @Override
    public void onMessage(Message rcvMessage) {
        try {
            messages.add(rcvMessage.getBody(String.class));
        } catch (JMSException ex) {
            LOG.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }
}

Листинг страницы index.jsp

На странице index.jsp определена библиотека jquery-3.2.1.min.js и JavaScript методы, которые используются для ajax-вызова сервлета. Интерфейс страницы включает текстовое поле 'prefix' и кнопки 'Отправить' и 'Получить'.

<%@ page language="java" contentType="text/html; charset=UTF-8"	
                         pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
                      "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
        <meta http-equiv="Content-Type" content="text/html; 
                                           charset=UTF-8">
        <title>JMS пример</title>
        <script src="js/jquery-3.2.1.min.js"
                       type="text/javascript"></script>
        <script>
           function sendMessages() {
                $.ajax({
                    url : 'service',
                    data : {
                        mode : 'send',
                        prefix: document.getElementById('prefix').value
                    },
                    success : function(response) {
                        $('#sendJMS').html(response);
                    }
                });
           }
           function receiveMessages() {
                $.ajax({
                    url : 'service',
                    data : {
                        mode : 'receive' },
                        success : function(response) {
                            $('#receiveJMS').html(response);
                    }
                });
          }
        </script>
  </head>
  <body>
      <h2>Пример JMS с использованием JBoss</h2>
      <p>Отправьте сообщения JBoss с префиксом 
                      <input id="prefix" type="text" size="5"></p>
         <input type="submit" width="80" value="Отправить" 
                                         onClick="sendMessages()"><p />
         <span id="sendJMS" style="color: #3f48cc;"> </span>

      <p>Получите сообщения из очереди JBoss</p>
         <input type="submit" width="80" value="Получить" 
                                         onClick="receiveMessages()"><p />
         <span id="receiveJMS" style="color: #7349a4;"> </span>
  </body>
</html>

Интерфейс страницы в браузере представлен на следующем скриншоте.

При нажатии на кнопку 'Отправить' вызывается JavaScript процедура sendMessages, определяющая значения параметров (mode, prefix) и вызывающая сервлет для отправки сообщений. Результат выполнения команды в виде набора сообщений, оформленных в HTML-коде, возвращается в функцию success, которая выкладывает его в поле sendJMS на странице. Если префикс не определен, то сервлет будет использовать по умолчанию значение 'prefix'. Как только сообщения попадут в очередь сервер вернет их объекту 'Receiver' по подписке.

При нажатии на кнопку 'Получить' вызывается JavaScript процедура receiveMessages, определяющая значение параметры mode и вызывающая сервлет для чтения полученных по подписке сообщений. Результат выполнения команды в виде набора сообщений, оформленных в HTML-коде, возвращается в функцию success, которая выкладывает его в поле receiveJMS на странице.

Старт сервера Wildfly

Сервер приложений Wildfly «логирует» всю информацию. При старте из IDE Eclipse эта информация дополнительно выводится в консоль. Ниже представлена информация (в сокращенном виде), связанная с настройкой сервером JMS очередей и стартом приложения «jms-jboss». В предпоследней строке отображено подключение MDB (Message Driven Bean) 'Receiver' к провайдеру HornetQ.


[org.hornetq.core.server] Server is now live
[org.hornetq.core.server] HornetQ Server version 2.4.5.FINAL
[org.hornetq.core.server] trying to deploy queue jms.queue.ExpiryQueue
[org.jboss.as.messaging]  Bound messaging object to 
                          jndi name java:/jms/queue/ExpiryQueue
[org.hornetq.ra]          HornetQ resource adaptor started
[org.jboss.as.messaging]  Bound messaging object to jndi name
                          java:jboss/DefaultJMSConnectionFactory
[org.jboss.as.messaging]  Bound messaging object to jndi name
                          java:jboss/exported/jms/RemoteConnectionFactory
[org.jboss.as.messaging]  Bound messaging object to jndi name 
                          java:/ConnectionFactory
[org.hornetq.core.server] trying to deploy queue jms.queue.testQueue
[org.jboss.as.messaging]  Bound messaging object to jndi name
                          java:jboss/exported/jms/queue/test
[org.jboss.as.messaging]  Bound messaging object to jndi name 
                          jms/queue/test
[org.hornetq.core.server] trying to deploy queue jms.queue.DLQ
[org.jboss.as.messaging]  Bound messaging object to jndi name
                          java:/jms/queue/DLQ
[org.jboss.weld.deployer] Processing weld deployment jms-jboss.war
[org.jboss.as.ejb3] (MSC  Started message driven bean 'Receiver'
                          with 'hornetq-ra.rar' resource adapter
[org.jboss.as.server]     Deployed "jms-jboss.war" 
                          (runtime-name : "jms-jboss.war")
   

Скачать пример

Исходный код рассмотренного примера в виде проекта Eclipse с использованием Maven можно скачать здесь (994 Кб).

Примеры взаимодействия Java приложения c Websphere MQ по отправки и чтению JMS сообщений представлены здесь.

  Рейтинг@Mail.ru