Взаимодействие с IBM WebSphere MQ через JMS API
Многим известен продукт IBM WebSphere MQ, предназначенный для обмена сообщениями между приложениями и обеспечивающий их гарантированную доставку. В данной статье я хотел бы поделиться личным опытом интеграции прикладного Java-ПО с этим продуктом. Я постараюсь не вдаваться в описание самого продукта (который является одним из лидеров в своей нише), т.к. в интернете масса ресурсов (в т.ч. и русскоязычных) по этой теме, которыми я нередко пользовался сам. Также, не буду глубоко описывать принципы применяемых J2EE-технологий. Вместо этого расскажу, как их использовать на конкретном примере.
Задача: обеспечить интеграцию программного обеспечения на платформе J2EE с IBM WebSphere MQ, для межсистемного взаимодействия в гетерогенной среде. Схема следующая:
- Есть наш софт — 1 шт.
- Есть софт, с которым нам надо взаимодействовать (внешние системы) — N шт.
- Для информационного обмена с каждой из внешних систем, на нашей стороне создаётся одна входящая очередь и одна исходящая очередь (стандартная схема асинхронного взаимодействия).
В J2EE существует API JMS (Java Message Service), описывающий технологию асинхронного обмена сообщений. Как и многие другие технологии J2EE, JMS является просто набором интерфейсов, реализуемым поставщиками конкретных продуктов. Для чего нужен API, не заполненный ни единой строчкой кода его реализации? Для того, чтобы обеспечить независимость программного обеспечения от поставщика услуг (в данном случае услуг доставки сообщений), и возможность его смены без изменения исходного кода ПО. IBM WebSphere MQ в числе прочего, предоставляет также и реализацию JMS API. Важно: при установке под Windows следует указать поддержку Java, по умолчанию она отключена.
Чтобы было с чем работать, сначала создаём объекты менеджера MQ. Кто не знает, что это и зачем нужно может сходить, например, сюда, либо поискать любые другие ресурсы. Советую вначале разобраться с объектами MQ, т.к. без этого моя статья будет трудна для понимания (см. выше — описываю не сам продукт, а его интеграцию с J2EE). В результате создаётся несколько объектов, из которых нас как программистов интересуют: менеджер MQ, входящая(-ие) очередь(-и), исходящая(-ие) очередь(-и). Для того, чтобы использовать стандартный API взаимодействия с WebSphere MQ — MQI, этого достаточно. Но мы используем технологии J2EE, поэтому работа администратора MQ продолжается.
Небольшое отступление. Обычно, технология JMS работает в тесной связке с технологией JNDI (Java Naming and Directory Interface). Грубо говоря, JNDI представляет собой API хранилища объектов, реализуемый различными поставщиками (самый распространённый и доступный поставщик услуг хранения — файловая система вашей операционной системы). Как это работает. JMS API описывает работу с объектами очередей и фабрик подключений к ним (в терминологии WebSphere MQ «фабрикой подключений» является «менеджер очередей»). Вопрос — как нам получить работающие объекты, если JMS содержит только интерфейсы? Ответ — считать их из JNDI-хранилища. Но «прежде, чем купить что-нибудь хорошее, надо сначала продать что-нибудь хорошее», т.е. прежде, чем считать объекты из JNDI-хранилища, следует их туда сначала сохранить. Посему возвращаемся из небольшого отступления обратно.
Итак, продолжаем администрировать MQ. Мой опыт основан на работе с сервером WebSphere MQ установленным на платформе Windows, к тому же, я программист, а не администратор, посему профессиональных админов (а особенно -иксоидов) прошу сильно не ругаться. Нам требуется сохранить данные о ранее созданных объектах MQ в каком-либо JNDI-хранилище, чтобы потом обратиться к ним из нашего софта. Для этого с MQ поставляется специальная утилита, которую требуется перед началом использования сконфигурировать под нашего провайдера JNDI API. Заходим в «Program Files\IBM\WebSphere MQ\Java\bin» (при установке по умолчанию), и открываем на редактирование конфигурационный файл «JMSAdmin.config». Я решил использовать того самого общедоступного поставщика услуг JNDI — файловую систему ОС Windows. Посему, указываем следующие значения свойств в конфигурационном файле:
INITIAL_CONTEXT_FACTORY=com.sun.jndi.fscontext.RefFSContextFactory
PROVIDER_URL=file:/C:/JNDI-Directory
SECURITY_AUTHENTICATION=none
Где:
INITIAL_CONTEXT_FACTORY
— поставщик услуг, лежит в «fscontext.jar», в папке «Program Files\IBM\WebSphere MQ\Java\lib». Если библиотеки там нет, найдите её среди ваших (или не ваших) J2EE библиотек и положите туда.PROVIDER_URL
— путь к хранилищу наших объектов. Надо создать пустую папку, в которой впоследствии будет автоматически создан файл с именем «.bindings», содержащий наши объекты в виде пар свойство=значение. Обратите внимание, что файл не имеет имени, только лишь расширение.
В «JMSAdmin.config» есть ещё несколько свойств, их описание находятся в комментариях там же. Нам они не нужны, и должны быть закомментированы символом «#». Следует внимательно проверить, чтобы все свойства, кроме трёх вышеуказанных, были скрыты в комментариях (не относится к опытным админам, знающим, что делать с этими свойствами).
Теперь мы готовы заняться непосредственно созданием объектов JMS в JNDI-хранилище, что и делаем, запустив «JMSAdmin.bat» из папки «Program Files\IBM\WebSphere MQ\Java\bin». Появится командное окно с приглашением вида «InitCtx>», в которое следует вводить специфические команды WebSphere MQ. После определенного количества экспериментов и наступаний на непонятные грабли, я пришёл к нижеописанным форматам команд.
Создание менеджера в JNDI-хранилище:
def qcf(имя) qmgr(имя) host(адрес) port(порт) ccs(кодировка) tran(CLIENT)
где:
qcf — имя фабрики подключений JMS. Это имя будет использоваться в софте.
qmgr — имя установленного менеджера WebSphere MQ,
host — сетевой адрес менеджера WebSphere MQ,
port — порт службы Listener менеджера WebSphere MQ, по умолчанию 1414,
ccs — кодировка, значение этого параметра должно соответствовать установленному для менеджера WebSphere MQ,
tran — тип связи, установить значение CLIENT
Чтобы не путаться, лучше указывать одно и то же значение для qcf и qmgr. Обычно создаётся только один менеджер.
Создание очередей в JNDI-хранилище:
(выполнить команду для каждой очереди, используемой в софте)
def q(имя) qmgr(имя) qu(имя) tc(MQ)
q — имя очереди сообщений в JNDI-хранилище Это имя будет использоваться в софте.
qu — имя очереди сообщений менеджера WebSphere MQ,
qmgr — имя установленного менеджера WebSphere MQ,
tc — тип клиента. Если ваши исходящие сообщения будут приниматься посредством обращения к очереди через JMS, указывайте значение (JMS). При этом в заголовке сообщения будут передаваться специфические для JMS данные. Если софт, принимающий ваши сообщения, работает не через JMS, то в начале всех сообщений будет видеть мусор. При указании значения (MQ) никаких дополнений к заголовку не будет, при этом сообщение будет корректно приниматься любыми платформами, в т.ч. и JMS. Я предпочитаю указывать значение (MQ) дабы в дальнейшем не задумываться о том, как будет приниматься сообщение на другой стороне.
Чтобы не путаться, лучше указывать одно и то же значение для q и qu.
Подсказка новичкам: многие поначалу путаются в типах очередей, поэтому знайте, что обычно софт обращается к двум очередям: входящей (тип Local Queue) и исходящей (в случае межсерверного взаимодействия — тип Remote Queue Definition; в случае обмена сообщениями через один сервер MQ — тип так же Local Queue). Никогда не пытайтесь обращаться к Local Queue с атрибутом Usage установленным как Transmission (используется в межсерверном взаимодействии).
На этом этапе задача администрирования MQ выполнена. Созданное JNDI-хранилище можно перенести в любое удобное место (например, на машину, с которой запускается ваш софт) путем простого копирования папки и её содержимого. Библиотеки MQ, подключенные к вашему проекту, обеспечат соединение с сервером, находящимся по указанному в первой команде (def qcf) сетевому адресу с любой машины, имеющей доступ к этому адресу.
Архитектура такова: софт должен знать только о настройках JNDI-хранилища (его местоположению) и иметь к нему доступ. Данные, которые мы вводили на этапе администрирования (а также автоматически добавляла в хранилище утилита JMSAdmin), в софте не нужны (за исключением имён объектов).
Двигаемся далее: непосредственно разработка ПО. На данном этапе у нас уже имеется установленный и настроенный сервер IBM WebSphere MQ, и JNDI-хранилище с JMS-объектами. Нам требуется:
- Объявить у себя в коде поля JMS-объектов, соответствующие интерфейсам JMS;
- Создать экземпляры JMS-объектов, считав их из JNDI-хранилища;
- При помощи созданных объектов соединиться с менеджером MQ, и начать непосредственно асинхронный информационный обмен.
1. Создаём проект и подключаем к нему библиотеки: jms.jar, fscontext.jar, com.ibm.mq.jar, com.ibm.mqjms.jar (первые две должны быть в наличие у J2EE-разработчика, последние две берём из «Program Files\IBM\WebSphere MQ\Java\lib»). Создаём некий класс и объявляем в нем следующие поля (из расчёта на информационный обмен с одной внешней системой):
javax.naming.InitialContext ctx;
javax.jms.QueueConnectionFactory QCF;
javax.jms.QueueConnection conn;
javax.jms.QueueSession session;
javax.jms.Queue queue_out;
javax.jms.QueueSender sender;
javax.jms.Queue queue_in;
javax.jms.QueueReceiver receiver;
2. Считывание JMS-объектов:
// свойства соединения с JNDI-хранилищем, настраиваются также, как и для JMSAdmin
java.util.Hashtable env = new java.util.Hashtable();
// INITIAL_CONTEXT_FACTORY указать тот же, что и соответствующее
// свойство в «JMSAdmin.config»
env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
// provider_url указать тот же, что и соответствующее свойство в «JMSAdmin.config» (в
// нашем примере – «file:/C:/JNDI-Directory»).
// Если JNDI-хранилище переносилось с сервера с MQ на машину с вашим ПО, указать
// путь к папке, в которой был сохранён файл с именем «.bindings».
env.put(javax.naming.Context.PROVIDER_URL, provider_url);
// соединяемся с JNDI-хранилищем
ctx = new InitialContext(env);
// ищем в JNDI-хранилище требуемые нам объекты
// ищем менеджер подключений
// connection_factory_name указать то же, что и имя в команде «def qcf(имя)» (см. выше)
QCF = (QueueConnectionFactory) ctx.lookup(connection_factory_name);
// ищем очереди
// queue_name_out и queue_name_in указать те же,
// что и имя в командах «def q(имя)» (см. выше)
queue_out = (Queue) ctx.lookup(queue_name_out);
queue_in = (Queue) ctx.lookup(queue_name_in);
// больше нам JNDI-хранилище не нужно, отключаемся
ctx.close();
// отправка текстовых сообщений (есть и другие типы сообщений, их изучаем самостоятельно)
// в гетерогенной среде наиболее универсальным форматом является текстовое сообщение,
// с содержимым с XML-разметкой.
// Совет: если в сообщении используются символы, отличающиеся от латиницы,
// надёжнее передавать их в виде кодов
// (например «У» представляет символ кириллицы «У»), во избежание
// возможных проблем с перекодировкой при передаче через различные ОС, или
// даже через одинаковые ОС, установленные с различной кодировкой.
// IBM WebSphere MQ имеет встроенные средства перекодирования, но в них учтены не
// все возможные варианты.
// соединяемся
conn = QCF.createQueueConnection();
conn.start();
session = conn.createQueueSession(true, QueueSession.AUTO_ACKNOWLEDGE);
sender = session.createSender(queue_out);
// отправляем сообщение
// text - строка с вашим сообщением. Размер ограничен возможностями MQ и
// настройками очереди
javax.jms.TextMessage message = session.createTextMessage( text);
sender.send(message);
session.commit();
// получение текстовых сообщений, простой вариант
receiver = session.createReceiver(queue_in);
// wait – период ожидания поступления сообщения в очередь,
// пока нет сообщений, поток будет заблокирован в этой точке,
// на указанный период
javax.jms.Message message = receiver.receive( wait);
System.out.println(((TextMessage)message).getText());
// Более интересный и «продвинутый» вариант:
// создаётся так называемый «слушатель сообщений» и регистрируется
// на входящей очереди. После регистрации слушателя, приложение
// уже не должно беспокоиться о проверке наличия сообщений во входящей
// очереди – при их поступлении MQ автоматически вызовет метод
// onMessage() зарегистрированного слушателя, и передаст в качестве
// параметра поступившее сообщение.
// Во время тестирования не забудьте – если программа завершилась,
// передавать сообщение уже некому, поэтому на данном этапе, после
// регистрации слушателя можно поставить либо бесконечный цикл,
// либо что-то вроде Thread.sleep(100000).
javax.jms.MessageListener listener = new MessageListener() {
public void onMessage(javax.jms.Message message) {
if (message instanceof javax.jms.TextMessage) {
System.out.println(((TextMessage)message).getText());
}
}
};
receiver.setMessageListener(listener);
// Thread.sleep(100000)
session.close();
conn.close();
Обычно при старте ПО, из конфигурационных файлов или БД считываются настройки JNDI-хранилища и имена объектов MQ, и выполняется регистрация слушателей очередей. По мере поступления входящих сообщений выполняется какая-либо их обработка, формирование ответа и передача его в исходящую очередь.
На этом, собственно, и всё, что хотелось рассказать об основах взаимодействия с IBM WebSphere MQ через JMS API.
Список литературы и полезных ссылок:
- Интеграция приложений на основе WebSphere MQ
- Основы использования JMS в IBM WebSphere Community Edition
- IBM MQSeries: архитектура системы очередей сообщений
- Java Message Service
- Java Naming and Directory Interface
- WebSphere MQ Information Center (Документация, поставляемая с продуктом IBM WebSphere MQ)
Владимир Смирнов
АО «New Age Technologies»
13 коментарів
Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.