Spring Конфигурация для JMS (Websphere MQ - SSL, Tomcat, JNDI, Non IBM JRE)

Предыстория: у меня есть относительно старое приложение, которое использует Websphere MQ для обмена сообщениями. Он работает на WAS (Websphere Application Server) и использует MDB (Message Driven Beans). Мне удалось заменить все MDB с помощью Spring Integration - JMS. Мой следующий шаг - попытаться выяснить, могу ли я перенести его из WAS, чтобы он мог работать в любом другом контейнере сервлетов с JRE не IBM (я пытаюсь: apache tomcat). Обратите внимание, что защита каналов с использованием SSL является обязательным требованием. Я предпочитаю использовать JNDI.

Конечная цель: отделить мое приложение от сервера приложений (WAS) и другой инфраструктуры, такой как обмен сообщениями (MQ). Но вывод этого из WAS на tomcat - первый шаг. Далее следует задача по обновлению моей инфраструктуры обмена сообщениями с помощью чего-то более масштабируемого. Это позволяет мне обновлять отдельные компоненты инфраструктуры, на которые опирается мое приложение, по одной за раз (сервер приложений, уровень обмена сообщениями, хранилище данных), не прерывая работу приложения.

Вопрос: Теперь моя задача - определить ресурсы JNDI для tomcat, которые могут обращаться к Websphere MQ. Я добился некоторого прогресса в этом, используя не-SSL каналы, которые я определил в файле context.xml следующим образом:

<Resource
   name="jms/qcf_sandbox"
   auth="Container"
   type="com.ibm.mq.jms.MQQueueConnectionFactory"
   factory="com.ibm.mq.jms.MQQueueConnectionFactoryFactory"
   description="JMS Queue Connection Factory for sending messages"
   HOST="localhost"
   PORT="1414"
   CHAN="CHANNEL_SANDBOX"
   TRAN="1"
   QMGR="QM_SANDBOX"/>
<Resource
   name="jms/SandboxQ"
   auth="Container"
   type="com.ibm.mq.jms.MQQueue"
   factory="com.ibm.mq.jms.MQQueueFactory"
   description="JMS Queue"
   QU="SANDBOX_Q"/>

Мой следующий шаг - заставить это работать с каналами SSL. Я понимаю ту часть, которая включает в себя настройку хранилищ ключей (создание и обмен файлами kdb и сертификатов), настройку каналов SSL в QM и т.д. У меня все это уже работает. Как заставить tomcat использовать мое хранилище ключей, набор шифров и т.д.? Указатели или рабочий пример были бы великолепны!

Примечание. В настоящее время я использую Spring Integration 4.2, Websphere MQ v8, Tomcat v9.

Я должен добавить, что сначала я попробовал все без JNDI. Итак, вот мой весенний jms non-ssl config без JNDI, который работает:

<bean id="mq-jms-cf-sandbox"
   class="org.springframework.jms.connection.SingleConnectionFactory">
    <property name="targetConnectionFactory">
    <ref bean="mqQueueConnectionFactory" />
    </property>
</bean>
<bean id="mqQueueConnectionFactory" class="com.ibm.mq.jms.MQQueueConnectionFactory">
   <property name="hostName" value="localhost" />
   <property name="port" value="1414" />
   <property name="queueManager" value="QM_SANDBOX" />
   <property name="transportType" value="1" />
   <property name="channel" value="CHANNEL_SANDBOX" />
 </bean>
 <bean id="jms-destination-sandbox" class="com.ibm.mq.jms.MQQueue">
   <constructor-arg value="SANDBOX_Q" />
    <property name="baseQueueManagerName">
    <value>QM_SANDBOX</value>
    </property>
    <property name="baseQueueName">
    <value>SANDBOX_Q</value>
    </property>
</bean> 

Ответ 1

Думаю, я наконец понял, как это сделать... вот краткое описание шагов. Если вам нужна дополнительная информация, дайте мне знать.

Pre-Reqs: Установлен WebSphere MQ Server (не менее v 8.0.0.2) Настройте каналы QM, SSL и non-SSL, создайте Qs и все, что вам нужно. Излишне говорить, что вам нужны банки Websphere MQ. Помните о любых ограничениях на лицензирование.

Шаг 1. Получите прямое соединение без SSL, без JNDI. Вам нужно будет использовать эти beans для настройки ваших JMS-слушателей на основе spring и JMS-шаблонов и т.д.

<bean id="mq-jms-cf-sandbox"
   class="org.springframework.jms.connection.SingleConnectionFactory">
    <property name="targetConnectionFactory">
    <ref bean="mqQueueConnectionFactory" />
    </property>
</bean>
<bean id="mqQueueConnectionFactory" class="com.ibm.mq.jms.MQQueueConnectionFactory">
   <property name="hostName" value="localhost" />
   <property name="port" value="1414" />
   <property name="queueManager" value="QM_SANDBOX" />
   <property name="transportType" value="1" />
   <property name="channel" value="NON_SSL_CHANNEL" />
 </bean>
 <bean id="jms-destination-sandbox" class="com.ibm.mq.jms.MQQueue">
   <constructor-arg value="SANDBOX_Q" />
    <property name="baseQueueManagerName">
    <value>QM_SANDBOX</value>
    </property>
    <property name="baseQueueName">
    <value>SANDBOX_Q</value>
    </property>
</bean>

Шаг 2. Получите прямое соединение с SSL, без JNDI. Я нашел, что это немного сложно.

2a. Поскольку я использовал JRE, отличную от IBM, я должен был убедиться, что спецификации шифров и наборов шифров необходимо настроить в соответствии с указанными здесь сопоставлениями: http://www-01.ibm.com/support/docview.wss?uid=swg1IV66840

Это, очевидно, означает, что мы, по крайней мере, должны обновить нашу Websphere MQ до 8.0.0.2. В моем случае я использовал ECDHE_RSA_AES_256_GCM_SHA384 на канале SSL и настроил jms beans в приложении для использования TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, например:

<bean id="mq-jms-cf-sandbox"
    class="org.springframework.jms.connection.SingleConnectionFactory">
    <property name="targetConnectionFactory">
        <ref bean="mqQueueConnectionFactory" />
    </property>
</bean>
<bean id="mqQueueConnectionFactory" class="com.ibm.mq.jms.MQQueueConnectionFactory">
  <property name="hostName" value="localhost" />
  <property name="port" value="1414" />
  <property name="queueManager" value="QM_SANDBOX" />
  <property name="transportType" value="1" />
  <property name="channel" value="SSL_CHANNEL" />
  <property name="SSLCipherSuite" value="TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"/>
</bean>
<bean id="jms-destination-sandbox" class="com.ibm.mq.jms.MQQueue">
<constructor-arg value="SANDBOX_Q" />
    <property name="baseQueueManagerName">
       <value>QM_SANDBOX</value>
    </property>
    <property name="baseQueueName">
       <value>SANDBOX_Q</value>
    </property>
</bean>

2b. Создайте сертификаты, хранилища ключей (kdbs), сертификаты обмена и т.д. Существует много способов сделать это. Но помните, что вам нужно будет хранить пароли, метка ключа для менеджера очереди должна быть "ibmwebspheremqqmgr" - все в нижнем регистре, без пробелов (без кавычек), ключевой ярлык должен быть как "ibmwebspheremquserid" - все в нижнем регистре, нет пробелов (без кавычек), где userid является идентификатором пользователя, который запускает tomcat. Если вам нужна подробная информация о том, как я это сделал, используя самоподписанные сертификаты, дайте мне знать.

. Теперь вы должны получить JVM, который работает tomcat, чтобы прочитать ваши хранилища ключей. Есть много способов, но здесь, как я это сделал: Создайте файл setenv.bat в папке bin tomcat со следующим содержимым (отладка SSL необязательна)

set JAVA_OPTS="-Djavax.net.ssl.trustStore=C:\path-to-keystore\key.jks" "-Djavax.net.ssl.trustStorePassword=topsecret" "-Djavax.net.ssl.keyStore=C:\path-to-keystore\key.jks" "-Djavax.net.ssl.keyStorePassword=topsecret" "-Djavax.net.debug=ssl" "-Dcom.ibm.mq.cfg.useIBMCipherMappings=false"

2d. Запустите tomcat, используя следующую команду:

catalina.bat run > ..\logs\tomcat.log 2>&1 

Чтобы остановить, просто нажмите ctrl + c (на окнах). Каким бы способом вы это ни делали, убедитесь, что setenv.bat используется во время запуска. Или используйте JAVA_OPTS для установки свойств хранилища ключей.

. Убедитесь, что использование канала SSL работает.

Шаг 3. Получите соединение JNDI, работающее с не-SSL, JNDI Многих было создать JNDI на tomcat. Вот как я это сделал: внутри веб-приложения создайте файл META-INF/Context.xml со следующим содержимым:

<Resource
   name="jms/qcf_sandbox"
   auth="Container"
   type="com.ibm.mq.jms.MQQueueConnectionFactory"
   factory="com.ibm.mq.jms.MQQueueConnectionFactoryFactory"
   description="JMS Queue Connection Factory for sending messages"
   HOST="localhost"
   PORT="1414"
   CHAN="NON_SSL_CHANNEL"
   TRAN="1"
   QMGR="QM_SANDBOX"/>
<Resource
   name="jms/SandboxQ"
   auth="Container"
   type="com.ibm.mq.jms.MQQueue"
   factory="com.ibm.mq.jms.MQQueueFactory"
   description="JMS Queue"
   QU="SANDBOX_Q"/>

Теперь в конфигурации spring вместо прямых конфигураций вам нужно всего лишь:

<jee:jndi-lookup id="mq-jms-cf-sandbox" jndi-name="java:/comp/env/jms/qcf_sandbox" resource-ref="false" />
<jee:jndi-lookup id="jms-destination-sandbox" jndi-name="java:/comp/env/jms/SandboxQ" resource-ref="false" />

Обратите внимание, что для краткости я просто не использовал ссылки на ресурсы. В случае, если вы это сделаете, еще несколько шагов, которые идут прямо.

Шаг 4. Теперь последний шаг - использовать канал SSL и JNDI. Предполагая, что вы сделали шаг 2, это легко. Измените META-INF/Context.xml со следующим содержимым:

<Resource
   name="jms/qcf_sandbox"
   auth="Container"
   type="com.ibm.mq.jms.MQQueueConnectionFactory"
   factory="com.ibm.mq.jms.MQQueueConnectionFactoryFactory"
   description="JMS Queue Connection Factory for sending messages"
   HOST="localhost"
   PORT="1414"
   CHAN="SSL_CHANNEL"
   TRAN="1"
   QMGR="QM_SANDBOX"
   SCPHS="TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"/>
<Resource
   name="jms/SandboxQ"
   auth="Container"
   type="com.ibm.mq.jms.MQQueue"
   factory="com.ibm.mq.jms.MQQueueFactory"
   description="JMS Queue"
   QU="SANDBOX_Q"/>

Обратите внимание на строку с SCPHS = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384". Если вам нужно установить другие такие параметры, см. Столбец "Краткая форма" в этой ссылке: https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.ref.dev.doc/q111800_.htm%23jm10910_?lang=en

Надеюсь, все это сработает для вас. Удачи!

Как только эта конфигурация работает, отправка сообщений довольно проста. Но вы можете слушать сообщение в очереди с помощью spring JMS Ссылка: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/jms.html

Шаг 1. Используйте spring DefaultMessageListenerContainer и настройте свой beans в XML файле, например: spring - beans.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"      
    "http://www.springframework.org/dtd/spring-beans.dtd">

   <!-- this is the Message Driven POJO (MDP) -->
   <bean id="messageListener" class="jmsexample.ExampleListener" />

   <!-- and this is the message listener container -->
   <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
       <property name="connectionFactory" ref="mq-jms-cf-sandbox"/>
       <property name="destination" ref="jms-destination-sandbox"/>
       <property name="messageListener" ref="messageListener" />
   </bean>

</beans>

Шаг 2. Добавьте это в свой web.xml

<context-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>/WEB-INF/context/spring-beans.xml</param-value>
 </context-param>

<listener>
       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

Шаг 3: напишите класс слушателя таким образом:

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

public class ExampleListener implements MessageListener {

    public void onMessage(Message message) {
        if (message instanceof TextMessage) {
            try {
                System.out.println(((TextMessage) message).getText());
            }
            catch (JMSException ex) {
                throw new RuntimeException(ex);
            }
        }
        else {
            throw new IllegalArgumentException("Message must be of type TextMessage");
        }
    }
}

В качестве альтернативы вместо шага 3, если вы используете интеграцию spring, вы можете сделать что-то вроде этого:

<int:channel id="jms-inbound"/>
    <int-jms:message-driven-channel-adapter
        id="jms-inbound-adapter" container="jmsContainer" channel="jms-inbound" 
        extract-payload="true" acknowledge="transacted" 
        message-converter="messagingMessageConverter" />

<beans:bean id="messagingMessageConverter" class="org.springframework.jms.support.converter.MessagingMessageConverter">
    </beans:bean>

Ответ 2

Я знаю, что это старый пост, и с тех пор многое изменилось, но я застрял с подобной проблемой. У меня есть приложение, которое в настоящее время развернуто в IBM WAS и использует MDB для чтения сообщений из очереди и сохраняет их в базе данных, а также использует распределенные транзакции. Хотя я написал код в SpringBoot, который читает сообщения из очереди (JMS) и сохраняет их в базе данных, но я не могу определить, как реализовать распределенные транзакции для отката сообщения очереди в случае любых сбоев БД. Также я буду признателен, если вы поможете мне понять, как я могу развернуть это приложение в tomcat. Большое спасибо!