-1Прикладное программирование в ТС Лекция 3-15 Лекция 3-15 3.2.3. Система RMI 3.2.3.1. Структура системы RMI 3.2.3.2. Реализация SPI для работы с реестром RMI 3.2.4. Реализация приложения RMI 3.2.4.1. Реализация удаленного интерфейса 3.2.4.2. Реализация удаленной службы и создание заглушки и каркаса 3.2.4.3. Реализация сервера RMI 3.2.4.4. Реализация клиента RMI 3.2.4.5. Запуск приложения RMI 3.2.5. Асинхронный обмен сообщениями в Java 3.2.5.1. Сообщение системы JMS 3.2.5.2. Соединение в системе JMS 3.2.5.3. Сеанс связи в JMS 3.2.5.4. Отбор сообщений 3.2.5.5. Хранение сообщений в JMS 3.2.5.6. Создание отправителей и получателей сообщений 3.2.3. Система RMI 3.2.3.1. Структура системы RMI В распределенных системах «клиент-сервер» клиент определяет услуги, которые ему необходимы, а сервер обеспечивает эти услуги. В RMI определение удаленной службы выполняется с использованием интерфейса, а реализация – с помощью класса. Система RMI поддерживает два класса, реализующие один и тот же интерфейс. Первый класс реализует услугу и выполняется на сервере, второй класс действует как представитель (proxy) для удаленной службы и выполняется на клиентском компьютере, как это показано на рисунке: Клиентская программа вызывает методы для объекта proxy, затем RMI посылает запрос удаленной виртуальной машине Java, которая реализует запрос. Если реализация содержит возвращаемое значение, оно передается обратно proxy, а затем клиентской программе. Реализация RMI базируется на трех уровнях абстракции. Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. -2Прикладное программирование в ТС Лекция 3-15 уровень заглушки и каркаса (Stub and Skeleton layer); перехватывает вызовы метода, выполняемые клиентом к ссылочной переменной интерфейса, и перенаправляет эти методы удаленной службе RMI. уровень удаленной ссылки (Remote Reference Layer); транспортный уровень (Transport Layer). Схема уровней системы RMI представлена на рисунке: Использование уровневой архитектуры дает возможность изменять реализации отдельных уровней (например, изменение протокола транспортного уровня) без последствий для всей системы. Уровень заглушки и каркаса перехватывает вызовы метода, выполняемые клиентом к ссылочной переменной интерфейса, и перенаправляет эти методы удаленной службе RMI. Для этого на клиентской машине создается класс заглушки (stub), а на сервере – класс каркаса (skeleton). Заглушка играет роль представителя удаленного объекта: принимает вызов метода удаленного объекта и передает параметры уровню удаленной ссылки, а также принимает от него результаты вызова. Каркас читает параметры метода, выполняет вызов метода удаленного объекта, принимает возвращаемое значение и передает его уровню удаленной ссылки. В реализации RMI для Java 2 для установления связи с удаленным объектом на сервере используются отражения (reflections) Java, поэтому класс каркаса становится излишним. Уровень удаленной ссылки интерпретирует ссылки клиента к удаленным объектам и управляет этими ссылками. Уровень обеспечивает объект RemoteRef, который предоставляет соединение с удаленным объектом. Объект заглушки использует метод invoke() объекта RemoteRef для выполнения вызова метода. В первых реализациях RMI для выполнения вызова метода удаленная служба должна быть предварительно активизирована. В настоящее время RMI поддерживает активизируемые удаленные объекты. Если клиент вызывает метод на сервере и соответствующий объект на сервере находится в «спящем» состоянии, RMI активизирует объект и десериализует его. Аргументы для удаленных методов и возвращаемые значения могут быть практически любого типа, включая удаленные и локальные объекты, а также примитивные типы. Более точно, передаваться и приниматься могут экземпляры примитивного типа, удаленного типа или сериализуемого объекта, т.е. объекта, реализующего интерфейс Serializable. Этим требования не удовлетворяют только некоторые объекты, например, дескрипторы файлов. При передаче аргументов и возврате значений выполняются следующие правила: Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. -3Прикладное программирование в ТС Лекция 3-15 удаленные объекты передаются по ссылке (ссылкой на удаленный объект является заглушка, выполняющая функцию proxy); локальные объекты передаются с помощью копирования, с использованием сериализации объектов (по умолчанию, копируются все поля, за исключением полей static или transient). Передача по ссылке означает, что любые изменения состояния объекта, вызванные вызовами удаленного метода, отражаются в первоначальном удаленном объекте. Передача с помощью копирования (передача по значению) означает, что в принимающей виртуальной машине создается копия объекта, и любые изменения в состоянии объекта отражаются только в этой копии. Транспортный уровень обеспечивает связь между виртуальными машинами Java клиента и сервера. Эта связь реализуется на основе протокола TCP сети Internet. Поверх TCP в RMI используется протокол удаленного метода Java – JRMP (Java Remote Method Protocol). Кроме того, в новой реализации RMI – RMI-IIOP вместо протокола JRMP используется протокол Internet для взаимодействия между ORB – IIOP (Internet Inter-ORB Protocol), где ORB – брокер запросов объекта (Object Request Broker), используемый в рассматриваемой далее архитектуре общего ORB – CORBA (Common Object Request Broker Architecture). Для того чтобы клиент мог найти удаленный объект на сервере, необходимо использовать службу наименования и каталогов. Система RMI может использовать имеющийся в Java интерфейс именования и каталогов Java – JNDI (Java Naming and Directory Interface). Однако в RMI чаще всего используется собственная простая служба именования – реестр RMI (RMI registry). Это небольшая база данных, хранящая список удаленных объектов, находящихся на этом сервере. Каждый удаленный объект зарегистрирован в реестре RMI под каким-то уникальным именем. Для работы с базой данных создается серверный процесс, тоже входящий в понятие реестра RMI. Этот процесс работает по протоколу TCP, он прослушивает по умолчанию порт 1099 и ждет запросы от клиента. Обращение к методам удаленного объекта происходит по следующей схеме. 1. Удаленный объект создается на сервере, регистрируется в реестре RMI, находящемся на том же сервере, и ждет запрос от клиента на выполнение своих методов. Кроме того, на сервере создается и хранится заглушка (stub) объекта. 2. Реестр RMI прослушивает порт, по умолчанию 1099, и ждет запрос от клиента. 3. Клиент, желающий обратиться к методам удаленного объекта, запрашивает у реестра объект по имени, зарегистрированному в реестре. 4. Реестр отыскивает заглушку объекта (stub), и пересылает ее клиенту. В заглушке, в частности, содержится местоположение (codebase) удаленного объекта. 5. Заглушка запрашивает у сервера описание методов удаленного объекта, указав его местоположение. 6. Сервер составляет список сигнатур методов удаленного объекта и отправляет их заглушке. 7. Клиент обращается к методам заглушки так, как будто это методы самого объекта. 8. Если аргументы метода – локальные объекты, то заглушка сериализует их и пересылает на сервер. Если аргументы – удаленные объекты, то пересылается ссылка на них. 9. Сервер десериализует параметры, передает их методу удаленного объекта, метод выполняется, результат сериализуется и пересылается заглушке. Та десериализует результат и передает клиенту. Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. -4Прикладное программирование в ТС Лекция 3-15 Таким образом, для клиентской программы работа с удаленным объектом производится так же, как с локальным объектом: создается экземпляр объекта и выполняются его методы. Связь с сервером, поиск удаленного объекта, пересылка аргументов метода серверу и результатов его выполнения обратно клиенту выполняет RMI. Интерфейсы и классы RMI определены в следующих пакетах: java.rmi – обеспечивает поддержку RMI; java.rmi.activation – обеспечивает поддержку технологии активации объектов RMI; java.rmi.dgc – обеспечивает классы и интерфейс для распределенной сборки мусора: DGC(distributed garbage-collection); java.rmi.registry – обеспечивает поддержку реестра RMI; java.rmi.server – обеспечивает поддержку серверной части RMI. 3.2.3.2. Реализация SPI для работы с реестром RMI Реализация SPI для работы с реестром RMI входит в стандартную поставку J2SE. Для ее активизации следует определить системное свойство java.naming.factory.initial следующим образом: Properties env = new Properties(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.registry.RegistryContextFactory"); Кроме того, надо задать адрес реестра RMI: env.puttContext.PROVIDER_URL, "rmi://server:1099"); Если это свойство не определено, то будет использован адрес "rmi:", означающий реестр RMI, работающий на той же самой машине localhost и прослушивающий порт с номером 1099. Вместо того чтобы определять эти свойства, можно всякий раз указывать полный адрес вида "rmi://[сервер]:[порт][/[объект]]" или "rmi:[/][объект]" с теми же правилами умолчания. Пространство имен, зарегистрированных в реестре RMI, «плоское» (flat), поэтому все эти имена рассматриваются системой JNDI как объекты класса CompositeName, но простые, состоящие из одного компонента. При сравнении имен учитывается регистр букв. Таким образом, установлены следующие значения синтаксических элементов: private Properties props = new Properties(); props.put("jndi.syntax.direction", "flat"); props.put("jndi.syntax.ignorecase", "false"); props.put("jndi.syntax.escape", "\\"); props.put("jndi.syntax.beginquote", "\"") ; props.put("jndi.syntax.beginquote2", "'"); Поэтому точки и другие спецсимволы, встречающиеся в именах реестра RMI, следует экранировать обратной наклонной чертой, апострофами или кавычками. По той же причине нельзя создавать вложенные контексты. При выполнении методов bind(), lookup() и других методов, описанных интерфейсом Context, поставщик услуг RMI обращается к соответствующим методам класса Registry системы RMI. Следовательно, каждый объект, хранящийся в контексте RMI, должен реализовывать интерфейс Remote. В контексте можно хранить еще и ссылки на объекты, реализующие интерфейс Referenceable. Само пространство имен реестра RMI всегда реализует интерфейс Referenceable, поэтому можно создать ссылку на реестр RMI в каком-нибудь Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. -5Прикладное программирование в ТС Лекция 3-15 контексте JNDI. Такая ссылка содержит адреса класса StringRefAddr. Тип каждого адреса – "url". Он должен быть строкой с адресом URL вида "rmi://хост:порт/объект" и содержать адрес RMI-реестра или удаленного объекта, зарегистрированного в нем, например: Context regCtx = (Context)ctx.lookup("rmi://host"); 3.2.4. Реализация приложения RMI Система RMI содержит следующие компоненты: определения интерфейса для удаленных служб (удаленного интерфейса); реализации удаленных служб; файлы заглушки и каркаса; сервер для удаленных служб; служба именования RMI для нахождения клиентом местоположения удаленных служб; Web-сервер или FTP-сервер для загрузки, при необходимости, байт-кода класса от сервера к клиенту и от клиента к серверу; клиентская программа, которая будет обращаться к удаленной службе. Для реализации приложения RMI необходимо выполнить следующие шаги: написать и откомпилировать программный код для интерфейсов; написать и откомпилировать программный код для классов удаленной службы; сгенерировать файлы классов заглушки и каркаса из классов реализации; написать и откомпилировать программный код для сервера; написать и откомпилировать код для программы клиента; установить и запустить систему RMI. Рассмотрим процесс реализации системы RMI на примере программы удаленного калькулятора. Клиентская программа направляет два числа на сервер, который выполняет над ними операции и возвращает результат клиенту (для упрощения программы предположим, что калькулятор выполняет только сложение и вычитание целых чисел). 3.2.4.1. Реализация удаленного интерфейса Удаленный интерфейс – это любой интерфейс, расширяющий интерфейс Remote из пакета java.rmi. Интерфейс Remote не содержит ни одного метода, он лишь указывает на то, что расширяющий его интерфейс является удаленным интерфейсом. Удаленный интерфейс обязательно должен иметь модификатор public, чтобы быть доступным для удаленных клиентов. Каждый метод удаленного интерфейса должен выбрасывать исключение класса RemoteException из пакета java.rmi или его суперкласса. Кроме того, в объявлении методов параметры или возвращаемые значения, являющиеся удаленными объектами, должны описываться типом своего удаленного интерфейса, а не реализующего его класса. Реализация интерфейса Calculator (файл Calculator.java) для калькулятора: import java.rmi.*; public interface Calculator extends java.rmi.Remote { // Метод сложения двух целых чисел public long add(long a, long b) throws RemoteException; // Метод вычитания двух целых чисел public long sub(long a, long b) throws RemoteException; Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. -6Прикладное программирование в ТС Лекция 3-15 } Откомпилированная программа помещается в файл Calculator.class. 3.2.4.2. Реализация удаленной службы и создание заглушки и каркаса Удаленный объект обязательно должен реализовать хотя бы один удаленный интерфейс, чтобы быть помеченным в системе RMI. Кроме того, методы hashCode(), equals(), toString(), обязательные для каждого объекта Java и определенные в классе Object, должны учитывать то обстоятельство, что все клиентские заглушки одного и того же удаленного объекта и возвращать значение true при их сравнении. Эти особенности учтены в абстрактном классе RemoteObject пакета java.rmi.server, расширяющем класс Object и переопределяющем эти методы. Этот класс реализует методы remoteHashCode(), remoteEquals(RemoteRef), remoteToString(), определенные в интерфейсе RemoteRef пакета java.rmi.server. Интерфейс RemoteRef определяет также очень важный метод public Object invoke(Remote obj, Method method, Object[] params, long opnum); который вызывает удаленный метод method удаленного объекта с заглушкой obj, передает методу параметры params и возвращает результат его выполнения. Параметр определяет хэш-код, который может использоваться для представления метода. Класс RemoteObject не реализует интерфейс RemoteRef явно, а использует его в своей внутренней реализации как защищенное (protected) поле ref. Класс RemoteObject служит суперклассом всех заглушек и удаленных объектов, используемых в системе RMI. Для создания удаленных объектов предназначено его расширение RemoteServer. Это тоже абстрактный класс, он не используется сам по себе, а служит суперклассом для создания классов удаленных объектов. В пакете java.rmi.server есть класс UnicastRemoteObject, расширяющий класс RemoteServer. Этот класс служит основой для создания удаленных объектов. Экземпляр этого класса существует все время работы той виртуальной машины Java, которая его создала. Для связи с заглушками, находящимися на машине клиента, объект класса UnicastRemoteObject создает сокеты потокового типа, работающие по протоколу TCP. Номер порта, который прослушивают эти сокеты, определяется произвольно во время выполнения, но его можно задать с помощью конструктора UnicastRemoteObject(int port) Кроме того, класс UnicastRemoteObject реализует операцию экспорта (export) удаленного объекта. Эта операция регистрирует удаленный объект в системе RMI и делает его видимым и доступным для клиентов. Экспорт осуществляется автоматически конструктором класса UnicastRemoteObject, который для этого обращается к одному из методов exportObject(), определенных в том же классе. Наиболее часто используется метод public static Remote exportObject(Remote obj, int port) throws RemoteException. Для создания удаленного объекта легче всего расширить класс UnicastRemoteObject. При этом в классе удаленного объекта обязательно должен быть конструктор, выбрасывающий исключение класса RemoteException или его суперкласса, поскольку его выбрасывает метод exportObject(). С учетом всего сказанного выше, реализация удаленного объекта для калькулятора (файл CalculatorImpl.java) выглядит следующим образом: import java.rmi.*; Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. -7Прикладное программирование в ТС Лекция 3-15 import java.rmi.server.*; public class CalculatorImpl extends UnicastRemoteObject implements Calculator { // Конструктор класса CalculatorImpl public CalculatorImpl()throws RemoteException { super(); } // Метод сложения двух целых чисел public long add(long a, long b) throws RemoteException { return a + b; } // Метод вычитания двух целых чисел public long sub(long a, long b) throws RemoteException { return a - b; } } Откомпилированная программа помещается в файл CalculatorImpl.class. После этого, для генерации программ заглушки и каркаса, запускается компилятор RMI – rmic (этот компилятор, так же как и компилятор javac, находится в подкаталоге bin SDK Java 2): rmic CalculatorImpl Этот компилятор создаст для программы калькулятора файлы программ заглушки (файл Calculator_Stub.class) и каркаса (файл Calculator_Skel.class). Если для компилятора rmic задать опцию -keep, то можно получить и просмотреть исходные тексты программ заглушки и каркаса. 3.2.4.3. Реализация сервера RMI После того как написан класс удаленного объекта, можно создавать его экземпляры – удаленные объекты. Программа, создавшая один или несколько удаленных объектов, называется сервером RMI. Эта программа должна сначала проверить, установлен ли менеджер безопасности класса RMISecurityManager или другого класса, расширяющего класс SecurityManager, и если нет, то установить его, а после создания удаленного объекта должна зарегистрировать его в реестре RMI. Регистрация удаленного объекта obj в реестре RMI производится одним из двух статических методов public static void bind(String url, Remote obj) public static void rebind(String url, Remote obj) класса Naming в пакете java.rmi. Разница между этими методами заключается в том, что метод bind() выбрасывает исключение класса AlreadyBoundException, если имя url уже связано с каким-то объектом, а метод rebind() просто заменяет объект. Другие методы класса Naming обеспечивают связь удаленных объектов и их клиентов с системой RMI. Так, метод public static void unbind(String url) удаляет имя url из реестра RMI, метод public static String[] list(String url) выдает список всех зарегистрированных серверов RMI, а метод public static remote lookup(String url); используется клиентом для поиска сервера RMI. Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. -8Прикладное программирование в ТС Лекция 3-15 Строка адреса url во всех этих методах строится как адрес URL, но со своими особенностями. Она имеет вид "//имя-хоста:порт/имя-сервера" В ней не указывается схема (подразумевается rmi:). Параметр имя-хоста задает символическое имя или IP-адрес сервера (если имя-хоста опущено, подразумевается localhost или 127.0.0.1). Параметр порт – это номер порта, который прослушивает реестр RMI (по умолчанию – 1099). Параметр имя-сервера задается произвольно, поскольку имя сервера RMI не связано с именем класса. Северную часть можно описать в методе main() класса удаленного объекта, превратив его в сервер RMI, а можно написать отдельный класс для сервера RMI. Реализация сервера RMI для калькулятора (файл CalculatorServer.java) выглядит следующим образом: import java.rmi.*; public class { // Конструктор класса CalculatorServer public CalculatorServer() { try { // Создание удаленного объекта Calculator c = new CalculatorImpl(); // Регистрация объекта в реестре RMI Naming.rebind( "rmi://localhost:1099/CalculatorService", c); } catch (Exception e) { System.out.println("Error: " + e); } } public static void main(String args[]) { new CalculatorServer(); } } Откомпилированная программа помещается в файл CalculatorServer.class. 3.2.4.4. Реализация клиента RMI Клиентское приложение, желающее обратиться к методам удаленного объекта, должно выполнить поиск удаленного объекта и связь с ним. Это делается по имени, зарегистрированному в реестре RMI, с помощью рассмотренного выше метода lookup() класса Naming. Аргументом метода является адрес удаленного объекта в виде строки URL, заданной по правилам, описанным выше. Следует отметить, что при создании экземпляра объекта указывается его интерфейс (например, Calculator), а не класс реализации (например, CalculatorImpl). Пример реализации RMI для калькулятора (файл CalculatorClient.java) приведен ниже: import java.rmi.*; import java.net.*; public class CalculatorClient { public static void main(String[] args) { try { // Поиск удаленного объекта Calculator c = Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. -9Прикладное программирование в ТС Лекция 3-15 (Calculator)Naming.lookup( "rmi://remotehost/CalculatorService"); // Вызов метода sub удаленного объекта System.out.println(c.sub(4, 3)); // Вызов метода add удаленного объекта System.out.println(c.add(4, 5)); } // Обработка исключений catch (MalformedURLException murle) { System.out.println("MalformedURLException" + murle.toString()); } catch (RemoteException re) { System.out.println("RemoteException"+ re.toString()); } catch (NotBoundException nbe) { System.out.println("NotBoundException" + nbe.toString()); } catch(ArithmeticException ae) { System.out.println("ArithmeticException" + ae.toString()); } } } Откомпилированная программа помещается в файл CalculatorClient.class. 3.2.4.5. Запуск приложения RMI Для запуска системы (на одном компьютере) необходимо запустить три консоли (окна MS DOS или программы Far): одну для сервера RMI, одну для реестра RMI и одну для клиента RMI. Предполагается, что все файлы приложения находятся в одном каталоге и текущим для консолей является этот каталог. Первым запускается реестр RMI с помощью программы rmiregistry (она находится в каталоге bin пакета Java 2 SDK): rmiregistry На второй консоли запускается сервер RMI с помощью команды: java CalculatorServer и, наконец, на третьей консоли запускается клиент RMI с помощью команды: java CalculatorClient После запуска на консоль будут выведены результаты вычитания и сложения. Если реестр RMI, сервер RMI и клиент RMI расположены на разных компьютерах, то файлы, необходимы для работы каждого компонента, должны быть предварительно, с помощью Web-сервера или FTP-сервера, пересланы на соответствующий компьютер. 3.2.5. Асинхронный обмен сообщениями в Java Все рассмотренные выше средства сетевой связи требуют, чтобы взаимодействующие сетевые программы находились в активном состоянии во время сеанса связи. Как говорят, это средства синхронной связи. Синхронную связь легко Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 10 Прикладное программирование в ТС Лекция 3-15 сделать двусторонней и многосторонней. Она отличается оперативностью и интерактивностью. Легко обеспечить надежность синхронной связи, просто подтверждая прием информации и повторяя передачу в случае потери сетевых пакетов. Во многих случаях не удается или просто не нужно обеспечивать синхронную работу распределенных приложений. Тогда организуется асинхронная связь. При асинхронной связи одна программа посылает информацию, порция которой называется сообщением (message), другой программе. После отправки сообщения программа продолжает свою работу. Надежность передачи в этом случае должна обеспечивать служба сообщений (message service) – промежуточное программное обеспечение, принимающее сообщение, хранящее его, и при первой возможности передающее сообщение программе-получателю. Служба сообщений может быть централизованной, работая как сервер на отдельной машине, или распределенной, встроенной в приложения, обменивающиеся сообщениями. В настоящее время разработчики стремятся сделать клиент «тонким» и чаще выбирают централизованную службу сообщений. Такая служба сообщений строится по одной из двух схем. Первая схема организует непосредственную передачу сообщения от отправителя к получателю (point-to-point, РТР). По этой схеме служба сообщений организует в оперативной памяти или на диске очереди сообщений для их временного хранения. Для каждого получателя сообщений организуется своя очередь, и только он может получить сообщения оттуда. Получатель может в любое время выйти на связь и извлечь сообщения из своей очереди. Сообщения передаются ему в том порядке, в котором они поступили в очередь. По этой схеме реализована, в частности, электронная почта. Вторая схема организует рассылку сообщений (publish-and-subscribe, Pub/Sub), опубликованных отправителями, всем подписчикам этих сообщений. Сообщений распределяются по нескольким разделам (topics). При подписке указывается один или несколько разделов, из которых подписчик хочет получать сообщения. Сообщения передаются ему в произвольном порядке. По этой схеме реализованы службы новостей, различные доски объявлений и рассылки. Служба сообщений является частным случаем промежуточного программного обеспечения, ориентированного на обработку сообщений – MOM (Message Oriented Middleware). Система MOM обеспечивает синхронную или асинхронную пересылку сообщений в распределенном приложении. Для связи клиентов с системой MOM она предоставляет API, который не зависит от операционной системы и сетевых протоколов. Системы MOM сейчас быстро завоевывают популярность. Организована ассоциация промежуточного программного обеспечения, ориентированного на сообщения – МОМА (Message Oriented Middleware Association), в который входят фирма IBM, выпускающая продукт MQSeries, корпорация Microsoft с продуктом MSMQ, фирма ВЕА, выпускающая MessageQ, корпорация Oracle, предоставляющая продукт AQ, фирма Sybase с системой сообщений dbQ и другие фирмы. Консорциум МОМА недавно переименован в международную ассоциацию промежуточного программного обеспечения – IMWA (International Middleware Association). Технология Java содержит службу сообщений JMS (Java Message Service), входящую в стандартную поставку J2EE. Интерфейсы и классы, составляющие это описание, содержатся в пакете javax.jms. Всякая реализация этих интерфейсов называется поставщиком (provider) JMS, в частности, любой сервер J2EE является поставщиком JMS Рассмотрим подробнее структуру JMS. Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 11 Прикладное программирование в ТС Лекция 3-15 3.2.5.1. Сообщение системы JMS Сообщение системы JMS строится таким образом, чтобы оно могло быть передано большинству систем MOM и правильно обработано этими системами. Каждое сообщение состоит из трех частей: заголовка (header), набора свойств (properties) и тела (body) сообщения. Тело сообщения может отсутствовать, у свойств могут быть пустые значения. Заголовок и свойства сообщения описываются интерфейсом Message. Заголовок сообщения по-разному создается и понимается различными системами MOM, поэтому спецификация JMS определяет заголовок в самой общей форме. По этой спецификации заголовок может содержать десять полей. Поле JMSRedelivered отмечает повторную доставку сообщения. Это поле устанавливается поставщиком JMS для повторной отправки сообщения получателю, если, например, получатель не подтвердил получение сообщения. Шесть полей определяются при отправке сообщения методом send() или publish(). Это идентификатор сообщения – поле JMSMessageID и адресат сообщения – поле JMSDestination. Третье поле, JMSDeliveryMode, хранит один из режимов получения сообщения: PERSISTENT, означающий, что в случае краха поставщика услуг следует приложить дополнительные усилия по доставке сообщения клиенту, например, сохранить сообщение в базе данных; NON_PERSISTENT, не требующий дополнительных усилий. Еще два поля, JMSPriority и JMSTimestamp, хранят приоритет сообщения, (приоритет возрастает от 0 до 9, значения 0-4 – обычные приоритеты, 5-9 – повышенные приоритеты, приоритет по умолчанию 4) и время передачи сообщения поставщику. Шестое поле заголовка, определяемое при отправке сообщения, поле JMSExpiration, определяет срок хранения сообщения. Нулевой срок означает неопределенное время хранения. Клиент определяет три поля заголовка. Поле JMSCorrellationID служит для связи сообщений, например, ответа с запросом, и имеет одно из трех значений. Это может быть идентификатор сообщения, назначаемый поставщиком JMS. Он начинается со строки "id:". Это может быть строка, содержание которой определяется приложением. Второе поле, JMSReplyTo, определяет адресата, которому надо переслать ответ на сообщение. Третье поле, JMSType, определяет тип сообщения. Интерфейс Message описывает методы getXxx() и setXxx(Xxx) для получения значений каждого из этих десяти полей и установки значений полей заголовка. Свойства сообщения задают признаки, по которым поставщик услуг JMS может фильтровать и сортировать сообщения перед отправкой получателю. Свойства сообщения определяются отправителем. Они представляют собой пары «имя – значение». Их значениями являются простые типы Java: boolean, byte, short, integer, long, float, double и тип String. Интерфейс Message описывает методы getXxxProperty(String name) и setXxxProperty(String name, Xxx value) для каждого из этих восьми типов. Кроме того, описаны методы public Object getObjectProperty(String name) throws JMSException и public void setObjectProperty(String name, Object value) throws JMSException. Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 12 Прикладное программирование в ТС Лекция 3-15 Их значениями являются объекты типов-оболочек Boolean, Byte, Short, Integer, Long, Float, Double и типа String. Спецификация JMS определяет несколько стандартных свойств сообщения. Их имена начинаются с префикса "jmsx". Набор стандартных свойств определяется соединением, в котором создаются сообщения. Список имен стандартных свойств данного соединения можно получить методом public Enumeration getJMSXPropertyNames() throws JMSException, описанном в интерфейсе ConnectionMetaData. В настоящее время определено девять стандартных свойств: JNSXAppID – идентификатор программы-отправителя (строка типа String ); JMSXUserID – идентификатор пользователя-отправителя (строка типа String); JMSXGroupID – идентификатор группы сообщений, к которой принадлежит сообщение (строка типа String); JMSXGroupSeq – порядковый номер сообщения в группе, начинается с 1 (число типа int); JMSXDeliveryCount – количество попыток доставки сообщения (число типа int) ; JMSXProducerTXID – идентификатор транзакции, в рамках которой отправлено сообщение (строка типа String); JMSXConsumerTXID – идентификатор транзакции, в рамках которой получено сообщение (строка типа String); MSXRcvTimestamp – время отправки сообщения (число типа long); JMSXState – состояние сообщения во временном хранилище: 1 – ожидает отправки, 2 – готово к отправке, 3 – время хранения сообщения истекло, 4 – сообщение сохранено. Имена свойств, начинающиеся со строки "jms_", означают, что свойство определяется поставщиком JMS. Имена всех свойств сообщения можно получить методом public Enumeration getPropertyNames() throws JMSException. Удалить все свойства сообщения можно методом public void clearProperties() throws JMSException. Система сообщений JMS представляет тело сообщения в одной из пяти форм. Они описаны пятью интерфейсами, расширяющими интерфейс Message. Самая обычная и простая форма тела сообщения, представляющая собой текст в виде строки типа String, описана интерфейсом TextMessage. В этом интерфейсе всего два метода получения текста из сообщения и занесения текста в сообщение: public String getText() throws JMSException public void setText(String string) throws JMSException. Вторая форма тела сообщения представляет собой поток, описанный интерфейсом StreamMessage, в который записываются простые типы данных Java, а также типы String, Object и массив типа byte[]. Запись выполняется с помощью методов writeXxx(Xxx value), а чтение данных – с помощью методов readXxx(). Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 13 Прикладное программирование в ТС Лекция 3-15 Третья форма, описанная интерфейсом MapMessage, передает пары «имя – значение». Типы этих пар такие же, как в потоке StreamMessage, записываются они методами writeXxx(String name, Xxx value), а их значения читаются методами readXxx(String name). Получить имена всех пар можно методом public Enumeration getMapNames() throws JMSException. Четвертая форма, описанная интерфейсом ObjectMessage, передает в сообщении сериализуемые объекты Java. Это делается с помощью двух методов: public Serializable getObject() throws JMSException public void setObject(Serializable obj) throws JMSException. Наконец, пятая, самая общая, форма тела сообщения, описанная интерфейсом BytesMessage, передает просто набор байтов. Основной метод public void writeBytes(byte[] value, int offset, int length) throws JMSException заносит в сообщение часть массива байтов value длиной length байтов, начиная от индекса offset. Симметричный ему метод public int readBytes(byte[] value, int length) throws JMSException читает массив байтов value, содержащий тело сообщения. Методы public void writeBytes(byte[] mess) throws JMSException public int readBytes(byte[] mess) throws JMSException записывают и читают весь массив целиком. Другие методы интерфейса BytesMessage читают и записывают в тело сообщения данные различных типов Java. Удалить все тело сообщения любой формы можно методом public void clearBody(). Сообщения создаются во время сеанса, а сеанс начинается после получения соединения с поставщиком JMS. Здесь соединение понимается не как процесс связи, а как объект, который надо получить. 3.2.5.2. Соединение в системе JMS В системе сообщений JMS соединение устанавливается либо для непосредственной пересылки сообщений получателю по схеме PTP, что описано интерфейсом QueueConnection, либо для рассылки сообщений подписчикам по схеме Pub/Sub. Этот тип соединения описан интерфейсом TopicConnection. Интерфейс QueueConnection определяет метод public QueueSession createQueueSession(boolean transacted, int ackMode) throws JMSException создания сеанса типа QueueSession, а интерфейс TopicConnection – аналогичный метод public TopicSession createTopicSession(boolean transacted, int ackMode) throws JMSException создания сеанса типа TopicSession. Если аргумент transacted равен true, то в течение такого сеанса создаются одна или несколько последовательных транзакций. Транзакцию образуют одно или несколько сообщений, которые отправляются все вместе или не отправляется ни одно из них. Это удобно в тех случаях, когда нужна гарантированная доставка всех сообщений получателю. Транзакцию можно завершить Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 14 Прикладное программирование в ТС Лекция 3-15 методом commit() интерфейса Session, после чего начинается следующая транзакция, или отказаться от ее выполнения методом rollback(). Метод commit(), кроме того, посылает своему сеансу подтверждение (acknowledgment) приема всех сообщений, входящих в транзакцию. Второй аргумент ackMode при этом игнорируется. Метод recover() останавливает передачу сообщений и возобновляет передачу только неподтвержденных сообщений. Если аргумент transacted равен false, то порядок подтверждения определяется вторым аргументом ackMode, который принимает одно из трех значений, описанных в интерфейсе Session: AUTO_ACKNOWLEDGE – получение сообщений подтверждается во время успешного выхода из метода received() или после успешной обработки получения сообщения методом onMessage(); CLIENT_ACKNOWLEDGE – получатель сам подтверждает прием данного и всех предыдущих сообщений методом acknowledge() интерфейса Message; DUPS_OK_ACKNOWLEDGE – «вялое» (lazy) подтверждение, выражающееся в повторной доставке сообщений при ошибке. Для того чтобы предоставить клиентам возможность соединения с поставщиком JMS, сам поставщик должен реализовать интерфейсы QueueConnectionFactory и TopicConnectionFactory. Методы public QueueConnection createQueueConnection() throws JMSException public QueueConnection createQueueConnection(String name, String password) throws JMSException public TopicConnection createTopicConnection() throws JMSException public TopicConnection createTopicConnection(String name, String password) throws JMSException этих интерфейсов возвращают соединения (объекты, а не процессы) типа QueueConnection или TopicConnection. Сами же объекты типа QueueConnectionFactory и TopicConnectionFactory поставщик JMS предоставляет всем желающим через какую-либо систему имен и каталогов, например, систему JNDI. Пример установления соединения «точка-точка» для непосредственной передачи сообщений и создания сеанса: Context ctx = new InitialContext(); QueueConnectionFactory qcf = (QueueConnectionFactory)ctx.lookup( "QueueConnectionFactory "); QueueConnection qc = qcf.createQueueConnection(); QueueSession qs = qc.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); Пример установления соединения типа Pub/Sub для рассылки сообщений: Context ctx = new InitialContext(); TopicConnectionFactory tcf = (TopicConnectionFactory)ctx.lookup( "TopicConnectionFactory" ); TopicConnection tc = tcf.createTopicConnection(); Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 15 Прикладное программирование в ТС Лекция 3-15 TopicSession ts = tc.createTopicSession(false, Session.CLIENT_ACKNOWLEDGE); Управление сеансом выполняется с помощью методов интерфейса Connection. Для того чтобы получатель смог принимать сообщения, соединение следует предварительно запустить с помощью метода public void start() throws JMSException, а остановить – с помощью метода public void stop() throws JMSException. После работы с соединением его следует закрыть с помощью метода public void close() throws JMSException close (), чтобы освободить занимаемые им ресурсы. Метод public ConnectionMetaData getMetaData() throws JMSException, описанный в интерфейсе Connection, позволяет получить сведения о соединении в виде объекта типа ConnectionMetaData. Кроме уже упоминавшегося метода getJMSXPropertyNames(), в интерфейсе ConnectionMetaData описаны методы public String getJMSVersion() throws JMSException public int getJMSMajorVersion() throws JMSException public int getJMSMinorVersion() throws JMSException, возвращающие версию системы сообщений JMS в виде строки, а также номер версии и ее модификации, и методы public String getJMSProviderName()throws JMSException public String getProviderVersion()throws JMSException public int getProviderMajorVersion()throws JMSException public int getProviderMinorVersion()throws JMSException, возвращающие имя и версию поставщика JMS в виде строки, а также номер версии и модификации. 3.2.5.3. Сеанс связи в JMS Сеанс связи является центральным понятием системы сообщений, описанным интерфейсом Session. Именно в рамках сеанса создаются отправители (providers) и получатели (receivers) сообщений, очереди сообщений, разделы для рассылки сообщений, а также сами сообщения. Сеанс, как правило, существует все время данного соединения, хотя его можно завершить методом close(). Интерфейс Session расширяет интерфейс Runnable, следовательно, сеанс выполняется отдельным подпроцессом. В рамках сеанса создаются сообщения, содержащие тело во всех пяти формах. Для этого используются методы Public BytesMessage createBytesMessage() throws JMSException public MapMessage createMapMessage()throws JMSException public ObjectMessage createObjectMessage() throws JMSException public ObjectMessage createObjectMessage(Serializable obj) throws JMSException public StreamMessage createStreamMessage() throws JMSException public TextMessage createTextMessage() throws JMSException public TextMessage createTextMessage(String text) Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 16 Прикладное программирование в ТС Лекция 3-15 throws JMSException. Еще один метод public Message createMessage() создает сообщение без тела, содержащее только заголовок и свойства сообщения. Сеансы, как и соединения, создаются для непосредственной передачи сообщений по схеме PTP или для рассылки сообщений по схеме Pub/Sub. Сеансы первого типа описаны интерфейсом QueueSession, расширяющим интерфейс Session. Методы этого интерфейса создают очередь сообщений с именем name: public Queue createQueue(String name) throws JMSException, временное хранилище сообщений: public TemporaryQueue createTemporaryQueue() throws JMSException, а также отправителя и получателя сообщений из очереди queue: public QueueSender createSender(Queue queue) throws JMSException public QueueReceiver createReceiver(Queue queue) throws JMSException public QueueReceiver createReceiver(Queue queue, String selector) throws JMSException и объект, позволяющий просматривать очередь и ее характеристики: public QueueBrowser createBrowser(Queue queue) throws JMSException public QueueBrowser createBrowser(Queue queue, String selector) throws JMSException. Аргумент selector задает фильтр для отбора сообщений, отправляемых получателю. Интерфейс QueueBrowser описывает метод просмотра очереди public Enumeration getEnumeration() throws JMSException, который возвращает содержимое очереди в порядке поступления сообщений. Кроме того, в интерфейсе QueueBrowser есть метод получения селектора сообщений, стоящих в очереди public String getMessageSelector()throws JMSException и метод получения очереди public Queue getQueue(String name) throws JMSException. После работы с объектом типа QueueBrowser его следует закрыть методом close(). Сеансы второго типа описаны интерфейсом TopicSession, тоже расширяющим интерфейс Session. Методы этого интерфейса создают раздел с именем name: public Topic createTopic(String name) throws JMSException, временное хранилище сообщений: public TemporaryTopic createTemporaryTopic() throws JMSException, отправителя и подписчика сообщений из раздела topic: public TopicPublisher createPublisher(Topic topic) throws JMSException; public TopicSubscriber createSubscriber(Topic topic) throws JMSException public TopicSubscriber createSubscriber(Topic queue, String selector, boolean noLocal) throws JMSException. Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 17 Прикладное программирование в ТС Лекция 3-15 Если аргумент noLocal равен true, то подписчик не будет получать сообщения, отправленные в рамках того же соединения. Можно создать неопределенного (undefined) отправителя или издателя, не связанного с очередью или разделом, указав в методе createSender() или в методе createPublisher() аргумент null. Такой отправитель или издатель при посылке сообщения методом send() или publish() обязательно должен указывать очередь или раздел, в который отправлено сообщение. Подписчик получает только те сообщения, которые поступили адресату во время активности подписчика. Система JMS позволяет создать долговременного (durable) подписчика, который получит и те сообщения, что поступили за то время, когда подписчик был не активен. Долговременного подписчика с именем name создают два метода интерфейса TopicSession: public TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException public TopicSubscriber createDurableSubscriber(Topic topic, String name, String selector, boolean noLocal) throws JMSException. Удалить долговременного подписчика можно методом public void unsubscribe(String name) throws JMSException. 3.2.5.4. Отбор сообщений У методов создания получателя есть аргумент selector, содержащий строкуфильтр для отбора сообщений, которые поставщик JMS передает получателю. Разные получатели могут получать разные сообщения из одной и той же очереди. Разные подписчики могут получать разные сообщения из одних и тех же разделов. Сообщения можно отбирать по их заголовкам и свойствам, но не по телу сообщения. Для того чтобы фильтр мог использоваться независимыми поставщиками JMS и системами MOM, строка selector создается по правилам составления условных выражений языка SQL92. Сообщение отправляется получателю, если это выражение будет истинно. Выражение разбирается слева направо с учетом приоритетов операций. Запись условных выражений в языке SQL92 отличается от записи логических выражений в языке Java. При составлении таких выражений действуют следующие правила. Строки-константы заносятся в апострофы, а не в кавычки; если апостроф встречается в строке, то он удваивается, а не экранируется обратной наклонной чертой, например, 'Д''Артаньян'. В константах различаются заглавные и строчные буквы. Два символа имеют особое значение: знак подчеркивания '_' используется для подстановки одного произвольного символа, знак процента '%' вызывает подстановку любого числа любых символов. Логические операции записываются словами not, and, or. В логических операциях используется трехзначная логика: TRUE, FALSE и UNKNOWN. Добавлены операции BETWEEN, LIKE, IN и IS. Символ экранирования метасимволов '_' и '%' определяется операцией ESCAPE, например, выражение "JMSXAppID like '\_%' escape '\'" будет истинно для любого значения переменной JMSXAppID, начинающегося со знака подчеркивания. Операция получения остатка от деления, "%", не используется. Неравенство записывается знаками меньше и больше – "<>". Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 18 Прикладное программирование в ТС Лекция 3-15 Арифметические операции и сравнения вещественных чисел с фиксированной точкой нельзя использовать в фильтре. Признак комментария SQL (два дефиса подряд) нельзя использовать в выражении. Служебные слова: TRUE, NOT и др, можно записывать и заглавными и строчными буквами. Именами переменных в фильтре служат имена полей заголовка сообщения и имена свойств сообщения, например: String selector = "JMSXUserID = 'admin' AND content = 'info' AND length < 1024"; 3.2.5.5. Хранение сообщений в JMS Поставщик JMS организует временные хранилища сообщений, полученных от отправителей перед отправкой их получателям. Они называются адресатами (destination) сообщений и описаны интерфейсом Destination. Интерфейс Destination не описывает методы и не определяет константы. Он служит просто признаком типа. Его расширяют два интерфейса. При непосредственной отправке сообщений по схеме PTP адресатами служат очереди (queues) сообщений, описанные интерфейсом Queue. При рассылке сообщений по схеме Pub/Sub адресатами служат разделы, описанные интерфейсом Topic. Интерфейсы Queue и Topic просто переопределяют метод toString() и описывают один метод получения имени очереди или раздела. Каждая очередь и каждый раздел при своем создании описанными выше методами createQueue() или createTopic() получают имя, которое можно узнать с помощью методов public String getQueueName()throws JMSException public String getTopicName()throws JMSException. (первый метод определен в интерфейсе Queue, второй – в интерфейсе Topic). Хотя адресат является временным хранилищем сообщений, он существует до окончания сеанса. Иногда возникает необходимость удалять адресата и создавать его вновь. Для этого интерфейсы Queue и Topic расширены интерфейсами TemporaryQueue и TemporaryTopic соответственно. Эти интерфейсы описывают временную очередь и временный раздел. Временные адресаты создаются в единственном экземпляре на каждый сеанс методами public TemporaryQueue createTemporaryQueue() throws JMSException или public TemporaryTopic createTemporaryTopic() throws JMSException интерфейсов QueueSession или TopicSession, описанными в предыдущем разделе. Временный адресат может удалить с помощью метода public void delete() throws JMSException, который определен и в интерфейсе TemporaryQueue и в интерфейсе TemporaryTopic. Создание очередей и разделов осуществляется, как правило, поставщиком JMS. У каждого сервера приложений есть свои средства для этого. У сервера J2EE, входящего в состав J2EE SDK, есть утилита администрирования j2eeadmin. Она работает из командной строки, например, для создания очереди или раздела с именем MyQueue следует набрать командную строку j2eeadmin —addJmsDestination MyQueue queue а для создания раздела с именем MyTopic – набрать командную строку Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 19 Прикладное программирование в ТС Лекция 3-15 j2eeadmin —addJmsDestination MyTopic topic По умолчанию сервер J2EE создает очередь с именем jms/Queue и раздел с именем jms/Topic. Посмотреть список созданных очередей и разделов можно с помощью команды j2eeadmin —listJmsDestination 3.2.5.6. Создание отправителей и получателей сообщений После создания очередей или разделов, а также адресатов, можно приступить к созданию отправителей (producers, senders), издателей (publishers), получателей (consumers, receivers) и подписчиков (subscribers). Для этого служат описанные выше методы createSender(), createReceiver(), createPublisher(), createSubscriber() и createDurableSubscriber() интерфейсов QueueSession или TopicSession. При создании отправителей и получателей указывается их адресат, а для получателя можно указать фильтр сообщений. Отправитель описывается интерфейсом MessageProducer. Методы этого интерфейса с модификаторами public void позволяют узнать и изменить значения по умолчанию некоторых полей заголовков сообщений: setPriority(int prior) – установить приоритет сообщений (по умолчанию 4); setTimeToLive(long millisec) – установить время хранения сообщений в миллисекундах (по умолчанию 0 мс); setDisableMessageTimestamp(boolean disable) – если аргумент равен true, то установить во всех сообщениях время равным 0; setDisableMessageID(boolean disable) – если аргумент равен true, то установить идентификатор всех сообщений null; setDeliveryMode(int mode) – установить режим доставки сообщений (по умолчанию DeliveryMode.PERSISTENT). Соответствующие методы getXxx() позволяют узнать установки этих полей по умолчанию. Все указанные методы бросают исключение JMSException. Отправитель любого типа может быть закрыт методом public void close() throws JMSException. интерфейса MessageProducer. Интерфейс MessageProducer расширен двумя интерфейсами: QueueSender и TopicPublisher, описывающими отправителя и издателя сообщений. Интерфейс QueueSender описывает несколько методов отправки сообщений. Основной метод public void send(Queue queue, Message message, int deliveryMode, int priority, int timeToLive) throws JMSException, кроме посылки сообщения message в очередь queue задает три поля заголовка. Остальные методы send() оставляют значения одного или нескольких полей по умолчанию. Если не указана очередь, то, по умолчанию, сообщение посылается в очередь, в которой создан отправитель этого сообщения. Неопределенный отправитель обязательно должен указать очередь, в которую посылается сообщение. Кроме методов send(), интерфейс QueueSender описывает еще только один метод public Queue getQueue() throws JMSException, возвращающий очередь, которая определила отправителя. Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 20 Прикладное программирование в ТС Лекция 3-15 Интерфейс TopicPublisher описывает несколько методов публикации сообщений. Основной метод public void publish(Topic topic, Message message, int deliveryMode, int priority, int timeToLive) throws JMSException. Этот метод и другие методы publish() аналогичны методам send(). Кроме методов publish(), интерфейс TopicPublisher описывает еще метод public Topic getTopic() throws JMSException, возвращающий раздел, который определил издателя. Получатель сообщений и подписчик рассылки описываются интерфейсом MessageConsumer. В нем сразу описаны методы получения сообщения: public Message receive() – получить следующее сообщение, как только оно будет доставлено адресату; public Message receive(long timeout) – ждать получения следующего сообщения timeout миллисекунд; public Message receiveNoWait() – получить следующее сообщение или вернуть null, если ни одно сообщение не доставлено адресату. Если во время обращения к методу receive() без аргументов сообщение еще не поступило адресату, то приложение, обратившееся к этому методу, блокируется до поступления сообщения. Второй метод ограничивает время ожидания, а третий метод вообще не ждет поступления сообщения. Все приведенные методы бросают исключение JMSException. Если надо получать сообщения асинхронно, то нет необходимости запускать подпроцесс для выполнения метода receive(). Для этого есть следующий метод интерфейса MessageConsumer: public void setMessageListener(MessageListener listener) throws JMSException. Он устанавливает в объект-получатель сообщений экземпляр слушателя сообщений. Слушатель сообщений – это класс, реализующий интерфейс MessageListener. В этом интерфейсе описан всего один метод: public void onMessage(Message message). Поставщик JMS обращается к этому методу, как только адресату поступило сообщение. Сообщение передается как аргумент message метода onMessage(). Реализация этого метода, заключающаяся в обработке полученного сообщения message, является задачей разработчика. Получатель сообщения может быть закрыт с помощью метода public void close() throws JMSException. Интерфейс MessageConsumer расширен двумя интерфейсами: QueueReceiver и TopicSubscriber, описывающими получателя сообщений и подписчика рассылки. Эти интерфейсы добавляют методы получения очереди и раздела соответственно: public Queue getQueue() throws JMSException public Topic getTopic() throws JMSException. После создания в предыдущих примерах сеанса qs для непосредственной передачи сообщений по схеме «точка-точка» и сеанса ts для рассылки сообщений по схеме Pub/Sub можно выполнить поиск объекта-очереди с именем, например, "stockQueue", с помощью системы имен JNDI: Queue q = (Queue)ctx.lookup("stockQueue"); Создадим в сеансе qs экземпляр отправителя сообщений в очередь q: Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 21 Прикладное программирование в ТС Лекция 3-15 QueueSender sender = qs.createSender(q); Затем создадим сообщение message с телом data в текстовой форме: TextMessage message = qs.createTextMessage(); String data = "Сообщение."; message.setText(data); Запустим созданное ранее соединение qc и пошлем сообщение data: qc.start(); sender.send(data); Для получения сообщений из очереди q, в сеансе qs создадим экземпляр получателя: QueueReceiver receiver = qs.createReceiver(q); Выполним прием очередного сообщения, например, в форме потока: StreamMessage message = (StreamMessage)receiver.receive(); После создания сеанса ts рассылки сообщений выполняем поиск объекта-раздела с помощью, например, системы имен JNDI: Topic t = (Topic)ctx.lookup("stockTopic"); В сеансе ts создадим экземпляр publisher издателя сообщений в раздел t, запускаем ранее созданное соединение tc и посылаем сообщение: TopicPublisher publisher = ts.createPublisher(t); tc.start(); publisher.publish(data); Для получения подписки создадим в сеансе ts экземпляр подписчика, а затем создадим экземпляр слушателя подписки и устанавливаем его в подписчик: TopicSubscriber subscriber = ts.createSubscriber(t); subscriber.setMessageListener(new MyListener()); Класс для прослушивания подписки задается следующим образом: public class MyListener implements MessageListener { public void onMessage(Message message){ String data = message.getText(); } } Файл: 681450345 Создан: 10.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А.