Появилась в одном проекте необходимость реализовать трансфер сообщений из JMS в веб-приложение конечному пользователю. Так как проект написан на Java EE 7, наиболее простым способом я счёт использование Websocket.
Передача сообщений из JMS в WebSocket
@MessageDriven(mappedName = "websocket.out") public class SimpleMessageListener implements MessageListener { @Inject private SimpleEndpoint endpoint; @Override public void onMessage(Message message) { try { endpoint.sendMessage(message); } catch (Exception ex) { ex.printStackTrace(); } } }
@Named @ApplicationScoped @ServerEndpoint(value = "/wse") public class SimpleEndpoint { public void sendMessage(Message message) throws IOException, JMSException { for (Session session : sessions) { if (session.isOpen()) { session.getBasicRemote().sendText(((TextMessage) message).getText()); } else { sessions.remove(session); } } } }
Тесты показали, что всё отлично работает.
Есть второй вариант, описанный Bruno Borges, который заключается в использовании CDI-событий. В данном случае JMS-листнер будет публиковать события, а эндпоинт будет их ловить. Изменения кода минимальны.
Изменения в JMS-листнере:
@Inject @JmsToWsMessage private Event<Message> event; @Override public void onMessage(Message message) { try { event.fire(message); } catch (Exception ex) { ex.printStackTrace(); } }Изменения в эндпоинте:
public void sendMessage(@Observes @JmsToWsMessage Message message) throws IOException, JMSException { for (Session session : sessions) { if (session.isOpen()) { session.getBasicRemote().sendText(((TextMessage) message).getText()); } else { sessions.remove(session); } } }Как видно, нам понадобится аннтоация, которой будут маркироваться события:
@Qualifier @Retention(RUNTIME) @Target({METHOD, FIELD, PARAMETER, TYPE}) public @interface JmsToWsMessage { }
Ещё один способ пришёл в голову буквально за утренним кофе. Для этого нужно:
1. Реализовать возможность доступа к websocket-сессиям из статических методов класса-эндпоинта
2. Сделать метод sendMessage статическим
И в JMS-листнере можно будет отправлять сообщения следующим образом:
SimpleEndpoint.sendMessage(message);
Передача сообщений из WebSocket в JMS
А что если потребуется трансфер в обратном направлении, из вебсокетов в JMS? Тут всё оказалось немного сложнее. Первый вариант, появившийся у меня в голове заключался во внедрении и использовании JMS-контекста в классе-эндпоинте.Способ оказался нерабочим. Как выяснилось позднее из статьи Bruno Borges, проблема заключается в баге, который не позволяет внедрять JMS-контекст в эндпоинты.
Второй способ заключался во внедрении ConnectionFactory, но и он у меня не сработал - любые JMS-ресурсы, внедрённые не в EJB, оказались null. Вполне возможно, что это из-за особенностей используемого мной сервера приложений (WildFly 8 с внедрённым ActiveMQ).
Но, раз осталась возможность внедрять JMS-ресурсы в EJB, появился третий вариант. Для его реализации создадим сессионный компонент, который будет отправлять сообщения:
@Stateless public class MessageSender { @Resource(mappedName = "java:/activemq/ConnectionFactory") private ConnectionFactory connectionFactory; @Resource(mappedName = "java:/jms/queue/websocket.in") private Queue queue; public void sendMessage(String message) { try { System.out.println("Sending to " + queue.getQueueName()); try (Connection connection = connectionFactory.createConnection()) { connection.start(); javax.jms.Session session = connection.createSession(true, javax.jms.Session.AUTO_ACKNOWLEDGE); MessageProducer producer = session.createProducer(queue); TextMessage textMessage = session.createTextMessage(); textMessage.setText(message); producer.send(textMessage); connection.stop(); } } catch (Exception ex) { ex.printStackTrace(); } } }Дело остаётся за малым - внедрить наш сессионный компонент в класс-эндпоинт. Но и тут оказался сюрприз - сработало только внедрение через конструктор, в остальных случаях я получал NullPointerException.
Изменения в WebSocket-эндпоинте
private MessageSender messageSender; public SimpleEndpoint() {} @Inject public SimpleEndpoint(MessageSender messageSender) { this.messageSender = messageSender; }В следующем посте я опишу решение данной задачи силами Springframework. По опыту работу с ним, могу предположить, что там всё будет просто.
Комментариев нет:
Отправить комментарий