/
Автор: Дейтел Х.М. Дейтел П.Дж. Сантри С.И.
Теги: языки программирования компьютерные технологии программирование язык программирования java
ISBN: 5-9518-0051-X
Год: 2003
Текст
Этот файл был взят с сайта
http://all-ebooks.com
Данный файл представлен исключительно в
ознакомительных целях. После ознакомления с
содержанием данного файла Вам следует его
незамедлительно удалить. Сохраняя данный файл
вы несете ответственность в соответствии с
законодательством.
Любое коммерческое и иное использование кроме
предварительного ознакомления запрещено.
Публикация данного документа не преследует за
собой никакой коммерческой выгоды.
Эта книга способствует профессиональному росту
читателей и является рекламой бумажных изданий.
Все авторские права принадлежат их уважаемым владельцам.
Если Вы являетесь автором данной книги и её распространение
ущемляет Ваши авторские права или если Вы хотите
внести изменения в данный документ или опубликовать
новую книгу свяжитесь с нами по email.
ТЕХНОЛОГИИ —
ПРОГРАММИРОВАНИЯ ПА
Х.М. Дейте л
II.Дж. Дейтел
СИ. Сантрп
AVA 2
:*»»»?А«
КНИГА 2
Распределенные
приложения
ТЕХНОЛОГИИ
ПРОГРАММИРОВАНИЯ НА
JAVA 2
КНИГА 2
Advanced
JAVA™ 2 Platform
How то Program
H.M. Deitel
Deitel & Associates, Inc.
P.J. Deitel
Deitel & Associates, Inc.
S.E. Santry
Deitel & Associates, Inc.
PRENTICE HALL, Upper Saddle River. New Jersey 07458
Х.М. Дейтел, П.Дж. Дейтел, СИ. Сантри
ТЕХНОЛОГИИ
ПРОГРАММИРОВАНИЯ НА
JAVA™ 2
КНИГА 2
РАСПРЕДЕЛЕННЫЕ ПРИЛОЖЕНИЯ
Перевод
с английского
под редакцией
А.И. Тихонова
Москва
Издательство БЖНОМ
2003
УДК 004.43
ББК 32.973.26-018.1
Д27
Перевод с английского
Левчука Ю.А., Тихонова А.И.
Х.М. Дейтел, П.Дж. Дейтел, СИ. Савтри
Технологии программирования на Java 2: Книга 2. Распределенные
приложения. Пер. с англ. — М.: ООО «Бином-Пресс», 2003 г. — 464 с: ил.
Предлагаемая книга является переводом второй части издания «Advanced Java 2
Platform. How to Program». Оригинал содержит более 1800 страниц, поэтому было принято
решение русское издание разбить на три части. Первая часть, книги посвящена созданию
графического пользовательского интерфейса, двухмерной и трехмерной графике,
компонентам Java Beans, взаимодействию с базами данных, вопросам обеспечения
безопасности.
Вторая часть книги, которую Вы держите в руках, посвящена распределенным
приложениям и на примерах знакомит с технологиями построенвл распределенных систем, а
также систем управления сетями: Remote Method Invocation (RM1), Jini, JavaSpaces, Java
Management Extensions (JMX), Jiro и построению гетерогенных систем на основе Common
Object Request Broker Architecture (CORBA). Рассматриваются различные подходы к
построению пиринговых приложений па основе RM1, Jini, JXTA.
В третьей части издания рассматривается создание серверных приложений и
корпоративных систем.
Все права защищены Никакая часть этой книги не может быть воспроизведена в любой форме или любыми
средствами, зтекгпронными или механическими, включая фотографирование, магнитную чапись или иные
средства копирования или сохранения информации без письменного разрешения издательства
Translation copyright О 2002 by Binom Publishers
Advanced Java 2 How to program, First Edition by Harvey Deuel. Copyright © 2002. All Right Reserved
Published by arrangement with the onginal publisher. PRENTICE HALL. iNC. a Pearson Education Company
© PRENTICE HALL, INC.
a Pearson Education Company. 2002
О Издание на русском языке.
Издательство Бином. 2003
На>ч!К1-то\ничсскос издание
Харви Дейтел, Пол Дейтел, СИ. Сантрн
Технологии программирования на Java 2
Книга 2. Распределенные приложения
Компьютерная верстка С.В Лычагниа
Подписано в печать 22.05.2003. Формат 70\100/16. Усл. псч. л. 37,7. Гарнитура Школьная.
Бумага пиетная. Печать офсет пая. Тираж 3000 экз Заказ 204
Издательство «Бином-Пресс». 2003 г.
170026, Тверь. Комсомольский просп.. 12
Отсчитано с готовых днапо1нтнвоа а ФГУП ордена Трудового Красного Знамени
«Техническая книга»
Министерства РФ по делам печати, телерадиовещания н средств массовых коммуникаций
198005, Санкт-Петербург. Измайловский пр.. 29.
ISBN 5-9518-0051-Х (рус.)
ISBN 0-I3-089560-1 (англ.)
Содержание
Предисловие 9
Особенности книги 10
Некоторые замечания для преподавателей 11
Подход к обучению 18
Благодарности 16
Об авторах 19
О компании Deitel & Associates, Inc 20
Консорциум World Wide Web (W3C) 20
Главе 1. Введение 21
1.1. Введение 22
1.2. Архитектура книги 23
1.3. Краткий путеводитель по книге 24
1.4. Выполнение примеров 26
Глава 2. Удаленный вызов методов 29
2.1. Введение 30
2.2. Практический пример. Создание распределенной системы
с помощью RMI 31
2.3. Определение удаленного интерфейса 32
2.4. Реализация удаленного интерфейса 33
2.5. Компиляция и выполнение сервера и клиента 46
2.6. Практический пример. Приложение Deitel Messenger
с активируемым сервером 48
2.6.1. Активируемый сервер приложения Deitel Messenger 49
2.6.2. Архитектура и реализация клиента в Deitel Messenger 58
2.6.3. Выполнение серверного и клиентского приложений
Deitel Messenger 74
2.7. Ресурсы в Internet и во Всемирной паутине 78
Глава 3. Jini 83
3.1. Введение 84
3.2. Установка Jini 85
3.3. Настройка среды Jini 86
3.4. Запуск обязательных сервисов 86
8.5. Выполнение LookupBrowser Jini 89
6 Технологии программирования на Java 2
3.6. Обнаружение 91
3.6.1. Обнаружение с однонаправленным вещанием 91
3.6.2. Обнаружение с групповым вещанием 96
3.7. Реализации сервиса и клиента Jini 100
3.7.1. Интерфейсы сервиса и классы поддержки 101
3.7.2. Посредник сервиса и реализации сервиса 103
3.7.3. Регистрация сервиса сервисом поиска 107
3.7.4. Клиент сервиса Jini 110
3.8. Знакомство со вспомогательными утилитами высокого уровня. . . 117
3.8.1. Утилиты обнаружения 117
3.8.2. Информационные утилиты 127
3.8.3. Утилиты аренды 129
3.8.4. Утилита JoinManager 133
3.8.5. Утилиты обнаружения сервисов 137
3.9. Ресурсы в Internet и во Всемирной паутине 138
Глава 4. Java Spaces 143
4.1. Введение 144
4.2. Свойства сервиса JavaSpaces 145
4.3. Сервис JavaSpaces 145
4.4. Обнаружение сервиса JavaSpaces 147
4.5. Интерфейс JavaSpace 149
4.6. Определение записи 150
4.7. Операция записи 151
4.8. Операции чтения и изъятия 154
4.8.1. Операция чтения 154
4.8.2. Операция изъятия 158
4.9. Операция уведомления 161
4.10. Метод snapshot 165
4.11. Обновление записей с помощью сервиса транзакций Jini 168
4.11.1. Определение пользовательского интерфейса 169
4.11.2. Обнаружение сервиса TransactionManager 171
4.11.3. Обновление записи 173
4.12. Практический припер.
Распределенная обработка изображений 177
4.12.1. Определение обработчика изображения 178
4.12.2. Разбивка изображения на фрагменты 184
4.12.3. Компиляция и выполнение примера 195
4.13. Ресурсы в Internet и во Всемирной паутине 196
Глава 5. Jova Management Extensions (JMX) 201
5.1. Введение 202
5.2. Установка 204
5.3. Практический пример 204
5.3.1. Ресурсы 204
5.3.2. Реализация агента управления JMX 219
5.3.3. Рассылка и получение уведомлений 222
5.3.4. Управляющее приложение , . 227
Содержание 7
5.3.5. Компиляция и выполнение примера 287
5.4. Ресурсы в Internet н во Всемирной паутине 238
Глава 6. Jiro 243
6.1. Введение 244
6.2. Установка 245
6.8. Запускаем Jiro 246
6.4. Динамические и статические сервисы 247
6.5. Динамические сервисы 248
6.5.1. Реализация динамических сервисов 249
6.6. Статические сервисы 258
6.6.1. Определение местоположения статических сервисов
с помощью класса ServiceFinder 258
6.6.2. Сервис событий 259
6.6.3. Сервис регистрации 267
6.6.4. Сервис планирования 268
6.7. Развертывание динамических сервисов 269
6.7.1. Использование динамических сервисов 272
6.8. Политики управления 285
6.8.1. Развертывание политик управления 295
6.9. Заключительные замечания по поводу
системы управления принтером 301
6.10. Ресурсы в Internet и во Всемирной паутине 308
Глава 7. CORBA. Часть 1 309
7.1. Введение 310
7.2. Последовательность действий 315
7.8. Первый пример. SystemCIock 317
7.3.1. SystemCIock.idl 317
7.3.2. SystemClocklmpl.java 319
7.3.8. SystemClockClient.java 323
7.8.4. Выполнение примера 326
7.4. Обзор архитектуры 827
7.5. Основы C0RBA 838
7.6. Пример AlarmClock 342
7.6.1. AlarmClock.idl 343
7.6.2 AlarmClocklmpl.java 343
7.6.3. AlarmClockCIient.iava 347
7.7. Распределенные исключения 350
7.8. Практический пример. Приложение Chat 354
7.8.1. chat.idl 356
7.8.2. ChatServerlmpl.java 357
7.8.3. DeitelMessenger.java 362
7.8.4. Выполнение приложения Chat 366
7.8.5. Обсуждение 367
7.9. Комментарии и сравнительный анализ 371
7.10. Ресурсы в Internet и во Всемирной паутнне 872
8 Технологии программирования на Java 2
Глава 8. CORBA. Часть 2 381
8.1. Введение 382
8.2. Интерфейс статических вызовов (SU), интерфейс динамических
вызовов (DII) и интерфейс динамических скелетов (DSI) 383
8.3. Адаптеры BOA, POA и TIE 387
8.4. Сервисы CORBA 389
8.4.1. Сервис именования 889
8.4.2. Сервис безопасности 390
8.4.3. Сервис объектных транзакций 391
8.4.4. Сервис устойчивых состояний 392
8.4.5. Сервисы событий и уведомлений 394
8.5. Компоненты EJB и CORBA 396
8.6. CORBA и RMI 402
8.6.1. Когда использовать RMI 402
8.6.2. Когда использовать CORBA 403
8.6.3. RMI-ПОР 404
8.7. Комплексный припер приложения.
RMIMessenger с использованием RMI-IIOP 405
8.7.1. ChatServer, реализованный с применением RMI-IIOP 405
8.7.2. Реализация ChatClient с применением RMI-ПОР 411
8.7.3. Компиляция и выполнение ChatServer и ChatClient 414
8.8. Пути развития 415
8.9- Ресурсы в Internet и во Всемирной паутине 416
Глава 9. Пиринговые приложения и JXTA 421
9.1. Введение 422
9.2. Клиент-серверные и пиринговые приложения 423
9.3. Централизоввивые и децентрализованные сетевые приложения . . 428
9.4. Поиск и обнаружение узлов сети 424
9.5. Практический пример. Deitel Instant Messenger 425
9.6. Определение интерфейса сервера 426
9-7. Определение реализация сервиса 428
9.8. Регистрация сервиса 435
9.9. Поиск других уалов 437
9.10. Компиляция и запуск практического примера 443
9.11. Доработка Deitel Instant Mea3enger 444
9-12. Реализация Deitel Instant Messenger на основе Multicast Sockets 444
9.12.1. Регистрация узла 444
9.12.2. Обнаружение других узлов 449
9.18. Введение в JXTA 460
9.14. Ресурсы в Internet и во Всемирной паутине 462
Предисловие
Жизнь состоит из фрагментов. Надо только их соединить.
Эдвард Морган Форстер
Добро пожаловать в волнующий мир передовых концепций программирования,
воплощенных в трех основных платформах Java: Java™ 2 Enterprise Edition
(J2EE), Java 2 Standard Edition (J2SE) и Java 2 Micro Edition (J2ME).
Принимая участие в работе конференции Internet /World Wide Web, которая состоялась
в ноябре 1996 г. в Бостоне, мы не могли предполагать, к каким результатам это
приведет, — четыре издания книги *Как программировать на Java» (книга, ставшая
мировым бестселлером среди книг по Java), а теперь и данная книга, посвященная
технологиям разработки программ на Java, которая, как мы надеемся, будет
полезной при углубленном изучении компьютерных технологий на старших курсах
высших учебных заведений, а также для профессиональных разработчиков.
До появления Java мы были убеждены, что в течение следующего десятилетия
C++ заменит С в качестве доминирующего языка разработки приложений и языка
системного программирования. Однако World Wide Web и Java способствовали
выдвижению Internet на первые роли при планировании и реализации
информационных систем. Организации хотят напрямую и без излтрних издержек интегрировать
возможности Internet в свои информационные системы. Java для этой цели подходит
больше, чем С+Н— согласно заявлению Sun Microsystems по состоянию на 2001 г.
более 95% корпоративных систем поддерживают Java 2 Enterprise Edition.
В книге мы рассмотрим технологии, которые незнакомы большинству
программистов на Java, но могут вызвать их интерес. Каждая глава построена таким
образом, чтобы читатель мог получить представление о передовых и достаточно
сложных технологиях Java, не углубляясь при этом во все нюансы. Фактически,
каждой нз рассматриваемых тем может быть посвящена отдельная книга объемом
600-800 страниц.
Для примеров в этой книге мы используем подход, отличный от того, который
применялся в других наших книгах. Количество программ уменьшилось, но эти
программы увеличились в объеме и иллюстрируют довольно изощренные приемы
проектирования программного обеспечения. Создавая книгу для разработчиков,
мы интегрировали в нее большое число технологий, чтобы продвинуться дальше
и экспериментировать с современными технологиями и широко используемыми
приемами проектирования программных систем. Это ли не лучший способ
научиться работать с реальными технологиями и программным кодом?
Прежде чем принять решение, какие темы включить в эту книгу, мы прочли
десятки книг и журналов, изучили информацию на Web-сайте Sun Microsystems
и приняли участие в многочисленных конференциях н презентациях. Мы
пересмотрели наш материал в свете новейших технологий, представленных на
конференции JavaOne, — конференции, спонсором которой выступила корпорация Sun
Microsystems, а ней приняли участие ведущие программисты на Java, — и других
10
Технологии программирования на Java 2
представительных конференциях no Java. Мы также изучили ряд книг по Java,
посвященных специальным темам. После проведения такого обширного
исследования мы создали основной набросок книги и отдали его иа рецензирование
специалистам по Java. Наше жаланне включить достаточно большое количество тем
привело к тому, что объем книги превысил 1800 страниц. Мы готовы принести
извинения за неудобства, связанные со столь большим объемом книги, но число
изучаемых тем и материал слишком обширны. При переиздании книги мы,
возможно, разделим ее на два тома1.
Эта книга существенно выиграла от ее тщательного рецензирования
многочисленной группой экспертов. Нам также очень помогла подробная документация,
которая имеется на Web-сайте корпорации Sim (www.auji.com). Рецензенты,
работающие в корпорации Sun и многих других известных компаниях, оказали большую
помощью при формировании структуры книги. Нам хотелось, чтобы опытные
программисты просмотрели наш текст и примеры кода, и мы могли предоставить
советы специалистов, которые реально работают с рассматриваемыми технологиями,
применяя ях на практике.
Особенности книги
Эта книга имеет следующие особенности:
• Отделка кода (Code Washing). Это наш новый термин для процесса,
который мы использовав и при форматировании исходного кода программ в
книге, чтобы сделать его предельно ясным для понимания и сопроводить
необходимыми пояснениями. Код разбит на небольшие фрагменты с большим
числом комментариев. Это значительно улучшает восприятие кода, что особенно
важно, так книга содержит очень большой объем кода.
• Распределенные системы. Корпоративные приложения обычно настолько
сложны, что эффективно работают только в том случае, когда программные
компоненты распределены по различным компьютерам н сети организации.
Эта книга знакомит с несколькими технологиями для построения
распределенных систем: Remote Method Invocation (RMI), Jini, JavaSpaces, Java
Management Extensiona (JMX), Jlro и Common Object Request Broker Architecture
(CORBA). Технология CORBA, поддержкой и раавитиам которой ванимается
организация Object Management Group (OMG), является сложившейся
технологией для интеграции распраделенных компонентов, написанных иа
различных языках программирования. Первоначально язык программирования Java
предназначался для сетевых программируемых устройств — в настоящзе
время эту роль выполняет технология Jini. JMX и Jlro представляют собой
технологии, специально предназначенные для управления сетями (локальными
сетями, глобальными сетями и т.д.).
• Применение паттернов проектирования. Наиболее крупные
практические примеры в книге содержат тысячи строк кода. Реальные системы, такие
как системы для банкоматов или для управления воздушным движением,
могут содержать сотни тысяч или даже миллионы строк кода. При построе-
В связи с этим при переволе было принято решение разбить русское издание на три
части. Книга, которую Вы держите в руках, является переводом второй части
оригинального издания «Advanced Java™ 2 Platform. How to Program» и посвящена распределенным
системам. Первая часть посвящена программированию графического пользовательского
интерфейса приложений, двухмерной н трехмерной графике, взаимодействию с базами
данных, компонентам JavaBeane. Вторая часть посвящена созданию распределенных
приложений, и, наконец, третья часть — созданию серверных приложений и
корпоративных систем- — Прим. ред.
Предисловие 11
нии таких сложных систем важную роль приобретает эффективное
проектирование. Sa последнее десятилетие в индустрии разработки программного
обеспечения был достигнут значительный прогресс в области паттернов
проектирования — проверенных архитектурных решений для построения
гибкого и хорошо управляемого объектно-ориентированного программного
обеспечения1. Использование паттернов проектирования может существенно
уменьшить сложность процесса проектирования. При создании программ
в этой книге мы использовали большое число паттернов проектирования.
• Пиринговые приложения. Пиринговые (peer-to-peer — Р2Р) приложения,
например, программы мгновенного обмена сообщениями и совместного
использования документов, становятся чрезвычайно популярными. Глава 9
знакомит с этой архитектурой, в которой каждый узел выполняет обязанности как
клиента, тек и сервера. JXTA (сокращение от «juxtapoeat — «помещать
рядом») определяет протоколы для реализации пиринговых приложений. Эта
глава содержит два практических примера Р2Р-приложений: одно написанное
С использованием Jini и RMI, а другое — с использованием многоадресных со-
кетов и RMI. Оба примера реализуют Р2Р-приложение мгновенного обмена
сообщениями. Мы планировали привести серьезный пример применения Jini
и решили, что будет уместно включить его в эту главу. Первый практический
пример в известном смысле является централизованным и, следовательно, не
является настоящим Р2Р-приложен нем (некоторые разработчики считают,
что Jini несколько избыточна для пиринговых приложений). Второй пример
демонстрирует облегченную, децентрализованную реализацию.
• Библиография и ресурсы. Главы этой книге содержат списки литературы
и унифицированные указатехи ресурсов (URL), предлагающие
дополнительную информацию по рассматриваемым технологиям. Это сделано, чтобы те
читатели, которые хотели бы более подробно изучить данную тему, могли
обратиться к ресурсам, которые мы сочли полезными.
Некоторые замечания для преподавателей
Мир объектно-ориентированного программирования
Когда мы писали первое издание книги «Как программировать на Java*,
университеты еще были ориентированы на процедурные языки программирования,
такие как Pascal и С. В некоторых университетах уже изучали
объектно-ориентированный язык программирования C++, но и здесь по большей части
использовалось процедурное программирование в сочетании с объектно-ориентированным
программированием — такое сочетание характерно для C++, ко не для Java. На
момент выхода третьего издания книги *Как программировать на Java* многие
университеты начали переходить с изучения C++ на изучение оенов Java, а
преподаватели стали склоняться к чисто объектно-ориентированному подходу к
программированию. Одновременно с этим сообщество разработчиков программных
систем стандартизировало подход к моделированию объектно-ориентированных
систем с помощью UML. Сложилась также практика применения паттернов
проектирования. Данная книга использует 100% объектно-ориентированный подход
и уделяет особое внимание паттернам проектирования.
Необходимым предварительным условием для чтения этой книги является
знакомство с нашей книгой *Как программировать на Java. Издание 4-е* (или
аналогичной книгой по Java), которая дает прочный фундамент для программирования на
1 Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влкссидес. «Приемы объектно-ориентированного
проектирования. Паттерны проектирования*. СПб.: Питер, 2001 г., 368 с.
12 Технологам программирования на Java 2
Java. Ниже перечислены главы и приложения, вошедшие в книгу *Как
программировать на Java. И здание 4-е», более подробное описание содержания можно найти на
сайте mnr.deitel.com: Введение в компьютеры, Internet и Web; Введение в
приложения Java; Введение в анплеты Java; Структуры управления, часть 1; Структуры
управления, часть 2; Мэтоды; Массивы; Программирование на основе объектов;
Объектно-ориентированное программирование; Строки и символы; Графика и Java 2D;
Компоненты графического интерфейса пользователя, часть 1; Компоненты
графического интерфейса пользователя, часть 2; Обработка исключений; Программные
потоки; Файлы и потоки; Сетевые средства; Мультимедиа: изображения, анимация,
аудио и видео; Структуры данных; Пакет утилит Java и манипулирование битами;
Коллекции; Инфраструктура Java Media и Java Sound; Java Demos; Ресурсы по Java;
Диаграмма приоритетов операторов; Набор символов ASCII; Системы счисления;
Разработка НТМЬ-документации с помощью javadoc; Интерфейсы событий и
слушателей Elevator; Модель Elevator; Представление Elevator; Unicode; Литература.
Почему студентам нравится Java
Студенты имеют высокую мотивацию, поскольку осознают, что изучают
перспективный язык (Java) и передовую парадигму программирования
(объектно-ориентированное программирование) для построения больших систем. Java
сразу же демонстрирует свои преимущества в мире, где Internet и Всемирной
паутине отводится основополагающая роль, а работодателям требуются
программисты для разработки сложных корпоративных систем. Студенты быстро
обнаруживают, что при помощи Java они достаточно быстро могут решать многие задачи,
поэтому они с удовольствием затрачивают дополнительные усилия на изучение
Java. Java помогает программистам реализовать свой творческий потенциал. Мы
хорошо видим это в ходе преподавания базового и расширенного курсов по Java,
которое осуществляет компания Deitel & Associates, Inc.
Главное назначение книги
Наша цель вполне очевидна — создать книгу по Java-технологиям для изучения
студентами старших курсов высших учебных заведений по специальностям,
связанным с информационными технологиями, для программистов на Java средней
квалификации, а также для более глубокого изучения ряда теоретических и
практических вопросов профессионалами. Чтобы удовлетворить этим требованиям, мы
написали книгу, которая, как мы надеемся, вызовет интерес у программистов на Java.
Мы представляем масштабные примеры приложений по изучаемым темам и часто
обращаемся к уже известному материалу. Мы следуем стилю и сложившейся
практике при написании исходного кода, при его форматировании, правильном
использовании интерфейсов прикладного программирования Java и технологий Java.
В этой книге представлены приложения Java, которые читатели могут использо-
зать, чтобы немедленно приступить к работе с рассматриваемыми технологиями.
Эволюция книги
Данная книга была закончена сразу же после книги *Как программировать на
Java. Издание 4-е*. Сотни тысяч студентов университетов и профессионалов по
всему миру изучали Java с помощью нашей книги. После своего издания книга *Как
программировать на Java* использовалась в университетах, корпорациях и
правительственных организациях по воему миру. Компания Deitel & Associates, Inc.
осуществляла преподавание курсов по Java для тысяч студентов, с использованием
написанных нами книг. Мы тщательно следим ва эффективностью подачи материала
и соответствующим образом меняем содержание наших книг.
Реализация концепций Java
Мы верим в Java. Реализация концепций Java корпорацией Sun Microsystems,
является просто блестящей. В основу нового языка были положены С и C++ — два
Предисловие
13
наиболее популярных и широко используемых в мире языка программирования.
Это немедленно привлекло на сторону Java большую группу
высококвалифицированных программистов, которые участвовали в реализации коммуникационных
систем, систем управления базами данных, приложений для персональных
компьютеров и системного программного обеспечения. Разработчики Sun отбросили
наиболее сложные и порождающие ошибки средства C/C++ (такие, как указатели,
перегрузка операторов, множественное наследование и некоторые другие). Языку
был придан компактный вид за счет удаления специализированных средств,
используемых лишь небольшой частью сообщества программистов. Язык стал
реально переносимым, что дало возможность реализовывать приложения для Internet
и Web. В него были включены такие необходимые для разработчиков средства,
как работа со строками, графикой, компоненты графического пользовательского
интерфейса, обработка исключений, организация многопоточной обработки,
мультимедийные возможности (изображения, анимация, аудио и видео), структуры
данных, взаимодействие с системами управления базами данных, сетевые средства
взаимодействия клиент/сервер на базе Internet и Web, технологии для создания
распределенных и корпоративных систем. Язык был сделан свободно доступным
для милхиоков программистов по всему миру.
2,5 миллиона разработчиков на Java
Язык программирования Java был создан в 1995 г. как средство для добавления
динамического содержимого в Web-страницы. Вместо статических текста и
графики Web-страницы могли быть теперь ожиалены с помощью аудио и видео,
анимации, интерактивных средств, а также трехмерных изображений. Однако помимо
этого в Java есть и множество других достоинств. Возможности Java полиостью
отвечают современным требованиям к обработке информации. Мы сразу же
разглядели имеющийся в Java потенциал, который позволил Java стать одним из
основных языков программирования общего назначения. Фактически технологии Java
вызвали революцию в разработке программного обеспечения, предоставив
возможности для создания активно использующего средства мультимедиа, не зависящего
от платформы объектно-ориентированного кода для широко используемых,
функционирующих в Internet и корпоративных сетях приложений и апплетов. Java
сейчас объединяет 2.5 миллионов разработчиков по всему миру — впечатляющее
достижение, если учесть, что история Java насчитывает всего семь лет. Ни один
другой язык программирования до сих пор не приобретал столь щирокую
аудиторию разработчиков за такое короткое время.
Подход к обучению
Данная кинга содержит множество примеров и упражнений из многих областей
практической деятельности, дающих читателю возможность решать интересные
практические задачи. Книга концентрирует внимание на принципах хорошего
стиля и техники программирования, что особенно важно при1 создании больших
программ, подобных рассматриваемым в этой книге. Мы старались избегать
хитроумных терминов и описания синтаксиса, отдавая предпочтение обучению на
примерах. Наши примеры были протестированы на популярных платформах.
Книга написана преподавателями, которые посвящают большую часть времени
обучению передовым технологиям практического программирования и написанию
книг по данной тематике.
Подход к обучению Java посредством живого кода (live-code™)
Книга насыщена примерами живого кода (live-code™). Это основа нашего метода
преподавания программирования и написания учебных пособий, а также основа
нашего мультимедийного курса обучения Cyber Classrooms и курсов дистанционного
Технологии программирования на Java 2
обучения на базе Web (Web-Baaed Training Courses). Каждое новое понятие
представлено в контексте законченной, работающей программы, за которой следует одна или
несколько врезок с входными/выходными данными. Мы называем такой стиль
обучения н написания учебных пособий методом живого кода (Uuecode™). Мы
используем язык программ для обучения языкам программирования. Чтение программного
кода во многом заменяет ввод его в компьютер и выполнение.
Программирование на Java, начиная со второй главы
В данной книге достаточно объемные примеры программ вы встретите уже во
второй главе. Читатель, таким образом, сразу же погружается в мир
распределенных приложений. Читатель на протяжении всей книги учится на основе
реализации больших проектов.
Доступ a World Wide Web
Исходный код примеров доступен для загрузки чераз Internet на следующих
Web-сайтах:
tnnr.d»ltel.сом
tnnr. pnnhall. com/d»itel
tnnr. Ыпот-ргма. ru/books/adv_j »v«2 . htm
Цели
Каждая глава начинается с раздела Цели. Здесь говорятся, чего ожидать и что
можно узнать, прочитав главу, чтобы читатели определились, нужен ли им
материал главы. Это повышает доверие и способствует позитивному восприятию.
Цитаты
За целями следуют цитаты. Некоторые из них юмористические, некоторые —
философские, некоторые представляют собой интересные точки зрения. Нашим
читателям нравится, что материал главы сопровождается цитатами. Многие из
цитат стоит прочитать снова после прочтения главы.
План
План главы помогает читателям подойти к изучению материала по принципу от
простого к сложному. Это также помогает заранее узнать, какие темы будут
рассматриваться дальше, а также установить для себя удобный и эффективный темп обучения.
Тысячи строк кода, большое число примеров программ
(с результатами их выполнения)
Мы представляем возможности Java в контексте законченных,
работоспособных программ. Программы в этой книге являются довольно объемными и
содержат сотни или даже тысячи строк кода. Читатели могут использовать программ-
ный код, загрузив примеры с сайта издательства, и запускать программы при
изучении книги.
Иллюстрации/рисунки
Многие из рисунков являются примерами кода, но в книге также имеется
множество диаграмм, рисунков и копий экранов с реэультетами выполнения программ.
Советы по программированию
Мы включили советы по программированию, чтобы помочь читателям
сосредоточиться на наиболее важных аспектах создания программ. Мы выделили эти советы
в виде рубрик Хороший стиль программирования, Типичная ошибка
программирования. Совет по отладке и тестированию, Совет по повышению эффективности,
Совет по переносимости программ. Общая методическая рекомендация и Внешний
вид программы. Эти советы и рекомендации представляют собой лучшее, что мы
собрали за несколько десятилетий активной разработки программного обеспечения
и преподавания. Одна из наших студенток — математик — говорила нам, что этот
подход подобен выделению аксиом, теорем и следствий в книге по математике; это
создает основу, на которой строится хорошее программное обеспечение.
—у. Хороший стиль программирования
|£>*1 ^ы вьи^еляем рекомендации по хорошему стилю программирования при
написании программ, которые позволяют создавать ясные, понятные, легко
отлаживаемые и сопровождаемые программы.
г&фл Типичные ошибки программирования
!<|г| Особое выделение типичных ошибок программирования помогает чита-
^^ телям не делать таких ошибок в своей работе.
—«t Советы по отладке и тестированию
\Ш& Когда мы впервые решили включить советы по тестированию и отладке,
^—*^ мы думали, что они будут подсказывать, как тестировать и
отлаживать программы на Java. Фактически большая часть этих советов
описывает различные аспекты Java, уменьшая вероятность возникновения
ошибок и, тем самым, упрощая процесс тестирования и отладки.
Советы по повышению эффективности
Мы включили в книгу советы, которые раскрывают возможности
повышения эффективности работы программ, — как сделать, чтобы
программы работали быстрее, или как минимизировать объем памяти, который
требуется для их выполнения.
Ш Советы по переносимости программ
Одним из основных преимуществ Java является переносимость, поэтому
некоторые программисты полагают, что если они реализуют приложение
на Java, это приложение будет автоматически обладать
переносимостью на все платформы, на которых имеется поддержка Java. К
сожалению, это не всегда так. Мы включили советы по переносимости, чтобы
помочь читателям научиться писать реально переносимый код и
предоставить информацию о том, как в Java достигается столь высокая
степень переносимости.
S Общие методические рекомендации
Парадигма объектно-ориентированного программирования требует
полного переосмысления способа построения программных систем. Java
является эффективным языком для разработки качественного программного
обеспечения. В этой рубрике выделяются архитектурные аспекты, кото
рые влияют на построение систем программного обеспечения, особенно
больших систем.
{Ива Внешний вид программы
^jjlj^ Мы предусмотрели эту рубрику, чтобы особо подчеркнуть соглашения.
З*™^ принятые при построении графического интерфейса пользователя. Эти
наблюдения помогут читателям разрабатывать графический интерфейс
пользователя в соответствии с принятыми нормами.
Резюме
Каждая глава заканчивается дополнительным методическим материалом. Мы
представляем подробный перачень основных положений, рассмотренных в главе.
16 Технологии программирования на Java 2
На каждую главу в среднем приходится по 26 пунктов резюме. Это поможет
читателям быстро просмотреть и закрепить ключевые понятия.
Терминология
Мы включили раздел Терминология, содержащий перечень всех основных
терминов, используемых в главе, в алфавитном порядке, — опять же для
закрепления изученного. На каждую главу в среднем приходится по 61 термину.
Упражнения для самоконтроля с ответами на них
Для самостоятельного изучения предусмотрены многочисленные упражнения
для самоконтроля и ответы на них. Это дает читателю возможность научиться
уверенно ориентироваться в изученном материале и подготовиться к выполнению
основных упражнений. Читателям следует попробовать выполнить все упражнения
для самоконтроля и проверить свои ответы.
Упражнения
Каждая глава завершается упражнениями. Преподаватели могут использовать
эти упражнения для домашних заданий, зачетов и экзаменов. Решения для
большинства упражнений включены в Руководство Инструктора и в Компакт-диск
Инструктора, доступные только для инструкторов чераз представителей
издательства Prentice-Hall. [ПРИМЕЧАНИЕ. Пожалуйста, не обращайтесь к вам
с просьбами предоставить Руководство Инструктора. Распространение их строго
ограничено преподавателями высших учебных заведений, которые
осуществляют обучение на основе этой книги. Инструкторы могут получить Руководство
только от официальных представителей издательства Prentice Hall. Мы
сожалеем, что не можем предоставить решения профессионалам.] Решения примерно
для половины упражнений имеются на мультимедийном CD-ROM Advanced Java 2
Platform Multimedia Cyber Classroom, который также является составной частью
полного обучающего курса The Complete Advanced Java 2 Platform Training Course.
Информацию для заказа вы можете найти, посетив наш Web-сайт tn™. deitel. com.
Благодарности
Нам очень хочется поблагодарить за проделанную работу многих людей, чьих
фамилий нет на обложке, но чья упорная работа, сотрудничество, дружеская
помощь и понимание сыграли очень важную роль в создании этой книги.
Многие сотрудники компании Deitel & Associates, Inc. посвятили не один час
работе над этой книгой.
• Джонатан Гэдзик, выпускник школы технических и прикладных наук
Колумбийского Унивзрситета (бакалавр компьютерных наук), принимал
участие в написании главы 1. Он также рецензировал главу 9.
• Су Занг, выпускник университета МакГилл, дипломированный специалист
в области компьютерных наук, является соавтором глан 3, 4, 5 и 6.
• Кайл Ломели, выпускник колледжа Оберлин, специализирующийся по
компьютерным наукам, является соавтором глав 5 и в. Он внес свой вклад в
главы 2 и главы 4.
• Вэтси ДюВалд, выпускница колледжа Метрополитен в Денвере,
дипломированный специалист в области технических средств общения (набор и
редактирование текстов) и имеющая вторую специальность в области
компьютерных информационных систем, является директором редакционного отдела
компании Deitel & Associates. Inc. Она принимала участие в написании
Предисловия, помогала готовить рукопись к публикации и редактировала
предметный указатель.
Предисловие
17
Мы также хотим поблагодарить участников программы College Internship
компании Deitel & Associates, Inc1.
• Крис Хенсон, студент-старшекурсник университета Врендейс
(компьютерные науки) рецензировал главы 3, 4, б.
• Одри Ли, студентка-старшекурсница колледжа Уэллесли,
специализирующаяся по компьютерным наукам и математике, внесла вклад в написание главы 2.
• Кристина Корней, студентка-старшекурсница, изучающая психологию и
бизнес в Высшей школе Фрэмжнгхэм, помогала готовить предисловие и
библиографию для нескольких глав.
• Эми Гипс, студентка-второкурсница Бостонского колледжа,
специализирующаяся по маркетингу и финансам, занималась подбором ссылок на ресурсы
для ряда глав. Эми также занималась подбором цитат для всей книги и
помогала готовить п ради С ловив.
Мы также хотели бы поблагодарить двух коллег по бизнесу, которые внесли
вклад в создание книги.
• Кэлби Зоргдрэгер выступал в качестве технического консультанта при
написании глав 3, 4, 5, 6. Он работает с Java, начиная с JDK 1.0. Последние 5 лет
Кэлби работал на Sim Microsystems в качестве инструктора по Java. Он
разрабатывал материалы для учебных курсов, слушателями которых стали
более 3500 студентов по всему миру. В последний год работы в Sun Кэлби
занимал должность инженера-программиста по разработке технологии Jiro.
Кэлби выступал на крупных международных конференциях, таких как Java On е.
В настоящее время Кэлби работает директором по разработке архитектуры
программных систем в eCar.Credit.com и использует Java при разработке
систем для компании Auto Finance Industry. В свободное время он
предоставляет независимые консультации и с удовольствием проводит врамя со своей
женой Бэт, дочерью Обри и сенбернаром Уинстоном. С Кэлби можно
связаться по адресу advanced_java@zorgdrager.org.
Нам выпало счастье работать над этим проектом вместе с группой талантливых
и квалифицированных специалистов по издательскому делу из Prentice Hall. Мы
особенно высоко ценим выдающиеся усилия редактора по компьютерным наукам
Петры Ректер и ее шефа — нашего наставника в издательском деле — Марши Хор-
тен, главного редактора отделения Технических и компьютерных наук
издательства Prentice Hall. Вине О'Бранен и Камилла Трентакост проделали громадную
работу по орган из алии издательского процесса.
Мультимедийный курс Advanced Java 2 Platform Multimedia Cyber Classroom
был разработан параллельно с данной книгой. Мы вполне оценили преимущества
нового подхода к обучению, прошедшего текническую экспертизу нашего главного
редактора по мультимедийному и компьютерному обучению, наставника и друга
Марка Тауба. Он и наш редактор но мультимедиа, Карен МакЛин. проделали
значительную работу по подготовке курса к публикации в сжатые сроки. Майкл Руэл
проделал огромную работу в качестве менеджера проекта Cyber Classroom.
Мы должны высказать особую благодарность в адрес Тамары Ньюнэм Кавалло
(smart_art@earthlink. net), кото рал проделала оформительскую работу с каши-
Программа College Internship Program компании Deitel ^Associates. Inc. предлагает ограни^
ченное количество платных вакансий для студентов колледжей в районе Бостона,
специализирующихся главный образом в области компьютерных наук, информационных
технологий, маркетинга или английского языка. Студенты работают в вашем офисе в Садбери,
штат Массачусетс полный рабочий день летом и/пли по вечерам в течение учебного года.
Для выпускников колледжей имеются вакансии не полный рабочий день. Для получения
более полной информации об этой конкурсной программе свяжитесь с Эбби Дейтел по
адресу deitel@dcitel.com пли справьтесь на нашем Web-сайте www.deitel.cnm.
Технологии профаммирования на Java 2
ми значками-рисунками к советам по программированию н с обложкой. Она
придумала этого восхитительного жука, который делится с вами советами по
программированию. Барбара Дейтел также помогала создавать рнсукки жуков, которых
вы видите на обложке.
Мы хотели бы отметить усилия наших рецензентов:
Элизабет Кальман (Национальная
библиотека Лос-Аламос)
Саяви Каруппасвами (ЕЮ)
Джоди Крогалис (Compuware)
Энтони Левенсейлор (Compuware)
Дерек Лэйн (президент компании
Gunslinger Software and Consulting, Inc.)
Рик Лов к (Callidus Software)
Ашиш Махиджанн (главный аналитик, про-
Джеф Аллея (Sun Microsystems)
Дибиенду Баск к (Sun Mi сговуз terns)
Тим Бодро (Sun Microsystems)
Пол Бирн (Sun Microsystems)
Окно Клуйт (Sun Microsystems)
Питер Корн (Sun Microeystems)
Петр Козел (Sun Microsystems)
Ион Найквист (Sun Microsystems)
Томас Павек (Sun Microsystems)
Мартин Риал (Sun Microsystems)
Даванум Сринивас (менеджер по JNI-FAQ,
Sun Microsystems)
Брэндов Тэйлор (Sun Microsystems)
Вики Аллан (университет штата Юта)
Джав&д Аслам (авалитик/разработчик
приложений, Tektronix)
Геири Болей (автор CORBA)
Кати Баршацки (Javakathy.com)
Дон Бешии (Ben-Cam Intermedia)
Кайт Видело у (Lutris)
Даррин Бишон (Levi, Ray and Shoup, Inc.)
Карл Бурнхэм (Southpoint)
Джон Копли (ишстнтут ДеВрай)
Чарльз Костарелла (колледж Antelope
Valley)
Джонатан Эрл (Technical Training
Consultants)
Джесс Глик (NetBeans)
Кен Гиллюр (Amdoca, Inc.)
Джейсон Гордон (Verizon)
Кристофер Грин (Colorado Springe Technical
Consultants)
Майкл Гай (XOR)
Дебора Хуке a (Mnemosyne Consulting)
Пол МакЛохлан (Compuware)
Рэнди Мейерс (NetCom)
Пол Манди (Imstion)
Стивен Ньютон (ведущий
программист/аналитик. Standard Insurance Company)
Виктор Питере (NextStepEducation)
Брайан Понтареллн (консультант)
Срикант Раджу (штатный инженер. Sun
Microeystems)
Робин Роу (MovieBditor.com)
Майкл Шмавьп (Aecenture)
Джошуа Шарф (Joshua Sharff Associates)
Дэн Шеллман (Software Engineer)
Йен Сигел (OMG)
Ума Саббах (Unigraphics)
Арун Таксани (jataayusoft)
Вэлим Ткаченко (Sera Nova)
Ким Топли (автор книг Core Java
Foundation Classes и Core Swing: Advanced
Programming, изданных Prentice Hall)
Джон Боргезе (университет Рочестер)
Ксиячжу Вант (Emerald Solutions)
Карен Бислевски (Titan Insurance)
Джесс Уплкинс (Metalinear Media)
В сжатые сроки эти рецензенты тщательно прочитали текст и внесли
многочисленные предложения, позволяющие повысить точность и полноту представления
материала.
Мы искренне заинтересованы в ваших комментариях, замечаниях, поправках
и предложениях по совершенствованию книги. Пожалуйста, посылайте всю
корреспонденцию па наш электронный адрес:
daitelgdeitel.com
Мы ответим незамедлительно. Кажется, все. Добро пожаловать в
увлекательный мир программирования на Java. Мы надеемся, что вам понравится наш
взгляд на разработку современных компьютерных приложений. С наилучшими
Д-р Харви М. Дейтел
Пол Дж. Дейтел
Шон Э. Сяк три
Предисловие
19
Об авторах
Д-р Харви М. Дейтел, руководитель фирмы Deitel & Associates, Inc., обладает
40-летним опытом в области информационных технологий, включая обширный
опыт работы в науке и промышленности. Он — один из ведущих в мире
преподавателей и руководителей семинаров в области компьютерных наук. Д-р Дейтел
получил степень бакалавра и магистра в Массачусетском технологическом институте
и степень доктора философии в Бостонском университете. Он работал над первыми
проектами операционных систем с виртуальной памятью а компании IBM и
Массачусетском технологическом институте, где были разработаны технологии, ныне
широко используемые в таких операционных системах, как UNIX, Linux и
Windows NT. Он обладает 20-летним опытом преподавания в высших учебных зазеде-
ниях, в том числе в качестве председателя Департамента компьютерных наук
в Бостонском колледже, где он работал до создания компании Deitel & Associates,
Inc., основанной совместно с Полом Дж. Дейтелом. Он является автором и
соавтором нескольких десятков книг и мультимедийных курсов, он продолжает писать
и сейчас. Работы д-ра Харви М. Дейтела были опубликованы в переводах на
японский, русский, испанский, китайский, корейский, французский, польский,
португальский и итальянский языки, они получили международное признание. Д-р
Дейтел проводил профессиональные семинары по всему миру для сотрудников
крупных корпораций, правительственных и военных учреждений.
Пол Дж. Дейтел, главный инженер компании Deitel & Associates, Inc.,
закончил Школу менеджмента Слоун Массачусетс ко го технологического института, где
он изучал информационные технологии. Работая в компании Deitel & Associates,
Inc., он преподавал программирование на Java, С, C++, а также обучал созданию
приложений для Internet и Всемирной паутины сотрудников различных
организаций, в том числе Sun Microsystems, EMC2, IBM, BEA Systems, Visa International,
Progress Software, Boeing, Fidelity, Hitachi, Cap Gemini, Corapaq, Art Technology,
White Sand Miasile Range, NASA (Космический центр имени Кеннеди), Computer-
vision, Cambridge Technology Partners, Adra Systems, Entergy, CableData Systems,
Banyan, Stratus, Concord Communications, а также многих других организаций. Он
читал лекции по C++ и Java в Бостонском отделении Ассоциации по
вычислительной технике, а также вел курсы по Java в совместном предприятии Deitel &
Associates, Inc., Prentice Hall и Technology Education Network. Он и его отец, д-р
Харви М. Дейтел, — авторы учебников по компьютерным наукам, ставших
мировыми бестселлерами.
Шон Э. Сэнтри, директор по разработке программных продуктов компании
Deitel & Associates, Inc., выпускник Бостонского колледжа, где он изучал
компьютерные науки и философию. Обучаясь в колледже, он выполнил оригинальное
исследование по применению метафизических систем при проектировании
объектно-ориентированного программного обеспечения. Работая в компании Deitel &
Associates, Inc., он преподавал ознакомительные и углубленные курсы для
сотрудников таких организаций, как Sun Microsystems, EMC3, Dell, Compaq, Boeing
и других. Он принимал участие в создании ряда публикаций Deitel, таких как
Java How to Program. Forth Edition (Как программировать на Java. Издание 4-е);
XML How to Program (Как программировать на XML); C++ How to Program,
Third Edition (Как программировать на C++. Издание 3-е); С How to Program,
Third Edition (Как программировать на С. Издание 3-е); e-Business and е-Сот-
merce How to Program (Как программировать приложения для электронного
бизнеса и электронной коммерции) и е-Business and e-Commerce for Managers
(Электронный бизнес и электронная коммерция для менеджеров). До поступления на
работу в Deitel & Associates он разрабатывал приложения для электронного
бизнеса в одной из ведущих в районе Бостона консалтинговых фирм.
20 Технологии программирования на Java 2
О компании Deitel & Associates, Inc.
Компания Deitel & Associates, Inc. является широко известной организацией,
осуществляющей обучение и специализирующейся на языках программирования,
Web-технологиях, объектных технологиях, электронном бизнесе и коммерции.
Компания Deitel & Associates, Inc. является членом консорциума Всемирной
паутины (W3C). Компания осуществляет обучение программированию на Java™,
C++, Visual Basic8, С, С#, Perl, Python, XML™, объектным технологиям,
созданию приложений для Internet и Всемирной паутины, электронного бизнеса и
электронной коммерции. Руководят компанией Deitel & Associates, Inc. д-р Харви М.
Дейтел и Пол Дж. Дейтел. В числе клиентов Deitel & Associates, Inc. многие
крупные компьютерные компании, правительственные учреждения, отделения
военных н коммерческих организаций. Благодаря сотрудничеству с издательством
Prentice Hall, компания Deitel & Associates, Inc. издает современные учебники по
программированию, книги для профессионалов, интерактивные мультимедийные
курсы серии Cyber Classrooms на CD-ROM и дистанционные обучающие курсы.
С компанией Deitel & Associates, Inc. и авторами книги можно связаться по
электронной почте по адресу
deitelgdeitel.com
Чтобы больше узнать о компании Deitel & Associates, Inc., ее публикациях
и программах обучения, посетите сайт:
www.deitel.сою
Компания Deitel & Associates, Inc. предлагает конкурсные вакансии в своей
Программе интернатуры для студентов в Бостонском регионе. Для получения
информации свяжитесь с Эбби Дейтел по электронному адресу
deitelgdeitel.com
Желающие приобрести книги Х.М. Дейтела П.Дж. Дейтела и учебные курсы
(Complete Training Courses) могут сделать это на сайте
www.deitel.can
Консорциум World Wide Web (W3C)
Г> Компания Deitel & Associates, Inc. является членом консорциума
World Wide Web Consortium (W3C). W3C был основан в 1994 г. для
.. разработки протоколов для Всемирной паутины. Как член W3C, мы
^^ участвуем в работе Совещательного комитета W3C (нашим
представителем в Совещательном комитете является Пол Дейтел). Члены Совещательного
комитета помогают вырабатывать стратегию W3C через проведение конференций
и встреч по всему миру. Организации-члены тан же помогают разрабатывать
рекомендации по стандартам для Web-технологий (таких как HTML, XML) путем
участия в деятельности W3C и рабочих группах Консорциума. Членами W3C могут
стать крупные компании и организации. Для получения информации о том, как
стать членом W3C, посетит» сайт www.w3.org/Consortium/Prospectus/Joining.
W3
1
Введение
Цели
• Познакомиться со структурой
• Узнать об особенностях
работы с примерами.
• Познакомиться с кратким
путеводителем по книге.
Прежде, чем начать, следует все
тщательно спланировать.
Марк Туллий Цицерон
Дела лучше всего идут, в самом
Блез Паскаль
Высокие мысли должны
излагаться высоким языком.
Аристофан
Наша жизнь растрачивается на
мелочи... Упрощайте, упрощайте.
Генри Торо
Благоволите смелому началу.
Вергилий
Думаю, я начинаю кое-что
в этом понимать.
Огюст Ренуар
22
Глава 1
1,1, Введение
Добро пожаловать в мир разработки приложений на платформе Java 2! Мы
много потрудились, чтобы создать книгу, которая, как мы надеемся, будет
информативной, увлекательной и познавательной1.
Технологии Java, с которыми вы познакомитесь, предназначены для
разработчиков программных систем. Книга рассчитана на читателя, который имеет
представление о программировании на Java и объектно-ориентированном
программировании, прочитав, например, книгу Как программировать на Java. Издание 4-е.
В нашей книге многие вопросы программирования на Java рассматриваются более
глубоко. Также обсуждается ряд новых тем, сопровождаемых тысячами строк
исходного кода и многочисленными иллюстрациями, позволяющими лучше понять
принципы программирования. Рассматриваемые технологии нашли свое
отражение в пользовательских приложениях н корпоративных системах,
демонстрирующих эти технологии во взаимодействии. Подобный подход мы называем методом
«живого кода» (Live-Code™).
Объектно-ориентированное программирование и паттерны проектирования
играют важную роль в разработке приложений и программных систем с
использованием многих рассматриваемых в книге технологий. Эти инструментальные
средства обеспечивают модульность, давая возможность программистам эффективно
разрабатывать классы в приложения. Паттерны проектирования особенно важны для
построения больших программ, которые мы представляем в этой книге.
Многие из рассматриваемых в этой книге приложений используют
возможности расширяемого языка разметки Extensible Markup Language (XML), который
фактически является отандартом для создания языков разметки, описывающим
структурированные данные олатфорыенно-независимым образом. XML могут
использовать все приложения, начиная от обычных Web-страннц и заканчивая
сложными системами бизнес-бизнес ( businessto-business — В2В). Принцип
переносимости данных XML дополняет принцип переносимости программ,
разработанных для платформы Java 2. Возможности XML для оннсания данных позволяют
системам, построенным на основе различных технологий, совместно использовать
данные, не заботясь о совместимости на уровне машинного кода, что очень важно
для разработки сложных, в том числе распределенных систем на Java. Мы
предполагаем, что читатель имеет представление о XML и об использовании XML в Java.
Данная книга является переводом второй части tAduaneed Java44 2 Platform. How to
Program*. Оригинал содержит более 1800 страниц, поэтому было принято решение
русское издание разбить на три части. Вторая часть, которую Вы держите в руках,
посвящена созданию распределенных прявожений. Первая часть посвящена графическому
пользовательскому интерфейсу, двухмерной и трехмерной графике, взаимодействию С базами
данных, компонентам JavaBeans, наконец, третья часть — созданию серверных
приложений и корпоративных систем. — Прим. ред.
Введение
23
Для получения необходимых сведений по XML и сопутствующих технологиях
можно обратиться, например, к нашей книге Как программировать на XML.
По мере чтения книги у вас может возникнуть желание обратиться к нашему
Web-сайту www.deitel.con для получения обновлений и дополнительной
информации по передовым технологиям, которые вы будете изучать.
1.2. Архитектура книги
При создании больших приложений эффективно или даже необходимо
выполнять задачи на различных компьютерах. Технологии распределенных систем дают
возможность выполнять приложения на нескольких компьютерах в сети. Чтобы
распределенная система корректно функционировала, компоненты приложения,
выполняющиеся на различных компьютерах в сети, должны уметь
взаимодействовать друг с другом. В книге представлено несколько технологий для построения
распределенных систем.
Глава 2 знакомит с технологией удаленного вызова методов (RMI — Remote
Method Invocation), которая позволяет объектам Java, расположенным на
различных компьютерах или исполняющимся под упраилением различных виртуальных
машин, взаимодействовать таким же образом, как если бы они находились на
одном компьютере или под управлением одной виртуальной машины. Каждый
объект вызывает методы других объектов, a RMI управляет маршалингом (т.е.
сборкой и пакетированием) параметров и возвратом значений, передаваемых между
удаленными объектами. Мы представляем несколько различных примеров RMI,
в том числе распределенное приложение для интерактивного общения (чата).
Java также предоставляет высокоуровневые API для построения
распределенных систем, включая Jini и JavaSpaces. Jini (глава 3) дает возможность
устройствам или программам в локальной сети взаимодействовать без необходимости
устанавливать специальные драйверы устройств и с минимальными затратами на
администрирование. Jini предоставляет реализацию принципа «подключил
и работай» для устройств — просто подключите принтер к сети, и сервисы этого
принтера станут доступными для всех участников сети. JavaSpaces представляет
собой сервис Jini, который обеспечивает простой, но мощный интерфейс для
построения распределенных систем. Мы демонстрируем технологию JavaSpaces
(глава 4) на примере распределенного приложения для обработки изображений-
По мере увеличения сложности сетей и роста зависимости успешной
деятельности компаний от этих сетей, возрастает роль упраиления сетями. Две
дополнительные технологии Java Management Extensions (JMX, глава 5) и Jiro (глава 6)
предназначены для построения распределенных приложений Java для управления сетями.
В главах 7-8 вы познакомитесь с архитектурой CORBA — Common Object
Request Architecture. CORBA дает возможность программам, написанным иа
разных языках и выполняющихся в различных точках сети, взаимодействовать друг
с другом так же легко, как если бы они находились в одном адресном
пространстве. В этих главах вы познакомитесь с основами CORBA И сможете сравнить CORBA
с другими технологиями построения распределенных систем, например, RMI.
Будет также представлена технология RMIIIOP, которая позволяет RMI
взаимодействовать с CORBA.
В глазе 9 будут обсуждаться основные концепции ниринговых приложений
(Р2Р — peer-to-peer), в которых каждое приложение выполняет функции и
клиента, и сервера, осуществляя таким образом распределенную обработку и хранение
информации на множестве компьютеров. Будут представлены две различные
реализации пирингового приложения для обмена сообщениями. Первая реализация
использует технологию Jini, а вторая — многоадресные сокеты и RMI.
24
Глава 1
1.3. Краткий путеводитель по книге
В этом разделе мы вкратце пройдемся по главам книги и выделим
многочисленные технологии Java, которые обсуждаются в книге. Возможно, вы встретитесь
с терминами, которые окажутся для вас незнакомыми, — оин будут определены
в последующих главах книги. В конце многих глав книги имеются разделы
«Ресурсы в Internet и во Всемирной паутине*, где перечислены Web-сайты, которые вам
следует посетить, чтобы получить дополнительную информацию по обсуждаемым
в этой главе технологиям. Вы также можете посетить Web-сайты www.deitel.com
и www.prenhall.com/deitel, где получите последнюю информацию, сведения об
ошибках и опечаткал и узнаете о дополнительных информационных и обучающих
ресурсах.
Глава 1. Введение
Эта глава содержит обзор технологий, представляемых в книге, и знакомит
с архитектурой книги. Мы включили в эту главу краткий путеводитель по книге
с обзором каждой из глав. Для рассматриваемых в книге примеров мы
представляем инструкции по установке и выполнению.
Глава 2. Удаленный вызов методов
Эта глава знакомит с технологией Remote Method Invocation (RMI),
предназначенной для построения распределенных систем ка Java. С помощью RMI объекты
Java, размещенные на разных компьютерах в сети, могут взаимодействовать точно
так же, как если бы они размещались на одном компьютере. Объекты Java могут
выполнять поиск для обнаружения удаленных объектов в сети и вызывать методы
через локальную сеть или даже через Internet. RMI соедает условия для
распределенного взаимодействия Java-объект — Java-объект, После того как объект Java
зарегистрирован как удаленный объект, клиент может «обнаружить» этот объект
Java и получить ссылку, которая позволяет клиенту удаленно использовать этот
объект. Синтаксис удаленного вызова методов идентичен синтаксису вызова
методов других объектов в той же программе. RMI обслуживает маршалинг (т.е. сборку,
упаковку и передачу) данных через сеть; RMI также дает возможность программам
ка Java передавать объекты Java с помощью механизма сериализации объектов
Java, Программисту не нужно знать обо всех деталях передачи данных через сеть.
Глава 3. Jini
Технология Jini представляет собой набор сетевых протоколов, программных
моделей и сервисов, которые дают возможность осущесталять реальные
взаимодействия по принципу «подключил и работай» между сетевыми устройствами и
программными компонентами, поддерживающими Jini. Технология Jini позволяет
разработчикам распределенных систем обнаруживать в сети и использовать
поддерживающие Jini ресурсы. Jini характеризуется устойчивостью и
использованием стандартизованных сетевых протоколов, включая протокол многоадресных
запросов, протокол групповых опоаещений и протокол обнаружения устройств.
Поддерживающие Jini ресурсы, или сервисы, используют эти протоколы для
нахождения других сервисов и взаимодействий с ними. Помимо сетевых протоколов,
технология Jini обеспечивает инфраструктуру, необходимую для того, чтобы
использовать эти протоколы. Эта инфраструктура представлена в виде набора классов,
которые скрывают низкоуровневую реализацию протоколов, давая возможность
разработчикам сосредоточить внимание на функциональных возможностях, а не на
реализации. В этой главе представлен обзор технологии Jini, сетевых протоколов,
которые поддерживают сервисы Jini, а также продемонстрированы возможности
Jini на примере достаточно большого приложения. Далее в книге (глава 9) мы
используем Jini при построении приложения для мгновенного обмена сообщениями.
Введение
25
Глава 4. JavaSpaces
Объекты, входящие в состав распределенных систем, должны иметь
возможность взаимодействовать друг с другом и совместно использовать информацию.
JavaSpaces представляет собой сервис Jini, который реализует простую,
высокоуровневую архитектуру для построения распределенных систем с использованием
распределенного хранилища объектов и трех простых операций: чтения, записи
и выборки. Сервисы JavaSpaces поддерживают проведение транзакций с помощью
менеджера транзакций Java, а также мехенизм уведомлений, который извещает
объект, если сервисом JavaSpaces записывается элемент данных, соответствующий
заданному шаблону. В первой половине главы мы представляем основные
концепции технологии JavaSpaces и используем простые примеры для демонстрации
операций, транзакций и уведомлений. В практическом примере в конце главы
сервисы JavaSpaces используются для построения приложения для обработки
изображений, в котором работа по применению фильтров распределена среди множества
программ на разных компьютерах.
Глава 5. Java Management Extensions (JMX)
Эта глава знакомит с расширением Java Management Extensions (JMX),
которое быхо разработано Sun и другими лидерами индустрии сетевых технологий для
определения компонентной инфраструктуры при построении интеллектуальных
приложений сетевого управления, включав уровень инструментальных средств,
уровень агента и уровень менеджера. Уровень инструментальных средств дает
возможность клиентам взаимодействовать с объектами (которые называются
управляемыми ресурсами) путем предоставления открытых интерфейсов к этим
объектам. Уровень агентов содержит агенты JMX, которые позволяют осуществлять
взаимодействие между удаленными клиентами и управляемыми ресурсами.
Уровень менеджера содержит приложения (клиенты), которые осуществляют доступ
и взаимодействуют с управляемыми ресурсами через агенты JMX. JMX также
обеспечивает поддержку существующих протоколов управления, например,
SNMP, чтобы разработчики могли интегрировать решения JMX с существующими
приложениями. В данной главе обсуждается архитектура JMX и представлен
практический пример, в котором используются возможности JMX для управления
имитатором сетевого принтера.
Глава 6. Лго
Эта глава служит введением в технологию Лго — технологию на базе Java,
которая предоставляет инфраструктуру для разработки решений для управления
распределенными ресурсами в гетерогенных сетях. Лго является реализацией
спецификации Federated Management Architecture (FMA), которая определяет
стандартный протокол для коммуникационного взаимодействия между гетерогенными
управляемыми ресурсами (устройствами, системами, приложениями). Технология
Jiro поддерживает трехуровневую архитектуру. Верхний уровень представляет
собой клиентский уровень. Клиент находит и взаимодействует с сервисами
управления. Средний уровень предоставляют как статические, так и динамические
управляющие сервисы. Нижний уровень состоит из гетерогенных управляемых
ресурсов. Технология Jiro является дополнением технологии JMX и может быть
использована для построения решений для управления сетами. В конце главы
рассматривается практический пример, схожий с примером применения JMX в
главе 5.
Глава 7. Архитектура CORBA. Часть 1
В этой главе вы познакомитесь с архитектурой Common Object Request Broker
Architecture (CORBA). CORBA представляет собой стандартную,
высокоуровневую, распределенную, объектную инфраструктуру для построения мощных и гиб-
26
Глава 1
ких приложений, ориентированных на сервисы. Мы исследуем наиболее важные
аспекты CORBA, определенные в спецификации Object Management Group (OMG).
Мы обсудим технологию Object Request Broker (ORB) — основу инфраструктуры
CORBA — н расскажем, каким образом она превращает CORBA в мощную
распределенную объектную инфраструктуру. Примеры «живого кода* демонстрируют,
как писать совместимый с CORBA респределенный код на Java. Демонстрируются
как клиентский, так и серверный компоненты JavalDL. В главе также
рассматривается практический приыер, в котором приложение Deitel Messenger реализовано
с помощью CORBA.
Глава 8. Архитектура CORBA. Часть 2
В этой главе продолжается обсуждение архитектуры CORBA. Мы
познакомимся с интерфейсом Dynamic Invocation Interface, а также с сервисами CORBA,
включая сервисы именования, безопасности, объектных транзакций, устойчивых
состояний. Приводится сравнительный анализ RMI и CORBA; мы также
познакомимся с технологией RMI-ПОР, используемой для интеграции RMI с CORBA.
Наконец, мы представим альтернативную реализацию приложения Deitel
Messenger с использованием RMI-ПОР.
Глава 9. Пиринговые сетевые приложения и JXTA
Приложения мгновенного обмена сообщениями и системы совместного
использования документов и файлов, такие как AOL Instant Messenger™ и Gnutella,
приобрели большую популярность. Они изменяют привычный способ взаимодействия
пользователей в сети. В пиринговом приложении каждый узел выполняет функции
как клиента, так и сервера. В таких приложениях ответственность за обработку
данных распределена между несколькими компьютерами, что позволяет
минимизировать требования к вычислительной мощности и объему памяти, а также
уменьшить вероятность сбоев, характерных для централизованных систем. В этой
главе вы познакомитесь с основными принципами построения пиринговых
приложений. Используя Jlni (глава 3), RMI (глава 2) и многоадресные сокеты, мы
представим практические примеры пиринговых приложений для системы мгновенного
обмена сообщениями. В первой реализации используются Jini и RMI, а во
второй — многоадресные сокеты и RM1. Наконец, мы познакомимся с JXTA
(сокращение от «juxtapose» — помещать рядом) — новой технологией с открытым
исходным кодом от Sun Microsystems'™, которая определяет протоколы для реализации
пиринговых приложений.
1.4. Выполнение примеров1
Мы обновляем инструкции по установке программного обеспечения на нашем
сайте www.deitel.com по мере выпуска новых версий Java SDK корпорацией Sun.
В примерах в книге используется стандартное соглашение по именованию
пакетов. Мы помещаем каждый из примеров в под пакет пакета com.deitel, названный
соответствующим образом. Например, пример WebBrowser, содержит следующее
объявление пакета:
package com.daitel.advjhtpl.gui.webbromar;
Акроним advjhtpl в имени пакета соответствует начальным буквам слов
оригинального названия книги {Advanced Java 2 Platform How to Program), 1 указывает
Промеры к книге можно загрузить по адресу http://www.deitel.com/books/downlo-
ads.html (раздел, посвященный книге Advanced Java™ 2 Platform How to program),
а также no адресу http://www.bmom-prees.ni/books/adv-java2.btiii. — Прим. ред.
Введение
27
на первое издание книги. Такая структура пакета требует, чтобы вы
компилировали примеры с использованием соответствующей структуры каталогов.
Управление пакетами с помощью компилятора командной строки Java может
оказаться излишне обременительным, поэтому мы рекомендуем читателям
использовать интегрированные среды разработки, чтобы упростить создание и
выполнение примеров и упражнений в этой книге. Мы использовали для работы
с примерами в этой книге интегрированную среду разработки Sun Forte for Java
Community Edition, которая является производной от NetBeans с открытым
исходным кодом (www.netbeans .org). Методические рекомендации по установке Forte
и использованию для разработки приложений вы можете найти, обратившись
к справочной системе Forte или к документации по адресу
www.Bun.com/forte/ffj/documentatlon/index.htnil
Большинство интегрированных сред разработки Java дают возможность
разработчикам загружать структуры каталогов, содержащие пакеты Java. Чтобы
облегчить работу с кодом, мы в примерах сохраняем структуру каталогов с
соответствующим размещением исходных файлов. Мы рекомендуем вам скопировать эту
структуру каталогов на жесткий диск своего компьютера. Скопировав структуру
каталогов, вы можете загружать примеры в соответствии с инструкциями для
вашей интегрированной среды разработки.
Для читателей, которые хотели бы использовать средства командной строки
для компиляции и выполнения программ, мы также предусмотрели отдельные
папки с примерами для каждой главы. Чтобы откомпилировать н выполнить
примеры из командной строки, скопируйте папку для определенной главы или
пример на ваш жесткий диск. Например, если вы скопируете каталог ch02 в каталог
C:\examples на вашем жестком диске, вы можете откомпилировать пример
WebBrowser с помощью команд
cd C:\examples\ch02\fig02_01
javac -d . WebBrowser.Java WebBrowserPane.Java HebToolBar.Java
Параметр —d . командной строки указывает, что компилятор Java должен
создать результирующие файлы .class в соответствующей структуре каталогов.
Чтобы выполнить пример, вы должны указать полное имя пакета для класса, в
котором определен метод main. Например,
Java com.deitel.advjhtpl.gui.webbrowaer.WebBrowser
В связи с разделением оригинала книги на три части приводим таблицу
соответствия между номерами глав второй части русского издания и номерами глав
оригинала (файлы примеров сохранили оригинальную нумерацию глав).
Номер главы
второй части
русского издания
1
2
3
4
5
Номер главы
оригинала
1
13
22
23
24
Номер главы
второй части
русского издания
6
7
8
9
Номер главы
оригинала
25
26
27
28
я
Удаленный вызов методов
Цели
• Освоить принципы
распределенного
программирования.
• Освоить архитектуру RMI.
активируемые объекты RMI
для построения гибких
распределенных систем.
• Понять, как использовать
обратные вызовы RMI.
• Научиться создавать
RMl-клиенты, которые
динамически загружают
необходимые классы.
• Научиться создавать
активируемые объекты RMI
В мире бизнеса иметь дело
более чем с одним клиентом
одновременно — все равно,
что иметь две жены.
Говорить одному клиенту,
что вы заняты с другим,
настолько неудобно, что вы
неизбежно начинаете лгать.
Эндрю Фротингэм
Благоволите к тем.
Кто терпеливо ждет.
Джон Мильтон
Правило 1: Клиент всегда прав.
Правило 2; Если вы думаете, что клиент
неправ, см. Правило 1.
Из лозунга в магазине
Мне нравится быть писателем. Но я не
выношу бумажной работы.
Питер де Врие
Глава 2
2.1. Введение
В этой главе мы познакомимся с возможностями распределенного
программирования на Java, рассмотрев технологию удаленного вызова методов (RMI — Remote
Method Invocation), RMI дает возможность выполнять объекты Java на различных
компьютерах или в отдельных процессах, взаимодействуя друг с другом
посредством удаленных вызовов методов. Такие вызовы методов выглядят для
программиста точно так же, как вызовы, оперирующие объектами в той же программе.
Технология RMI основана на более ранней схожей технологии удаленного
вызова процедур (RPC) для процедурного программироваиня, разработанной в 80-х
годах. RPC позволяет процедуре (т.е. программе, написанной на С или другом
процедурном языке программирования) вызывать функцию на другом компьютере
столь же легко, как если бы эта функция была частью программы,
выполняющейся на том же компьютере. Назначение RPC состояло в том, чтобы дать
возможность программистам сосредоточиться на выполнении необходимых для
приложения задач, вызывай для этого соответствующие функции, сделав прозрачным для
программиста механизм, позволяющий частям приложения взаимодействовать
через сеть. RPC выполняет всю работу по организации сетевых взаимодействий и мар-
шалинг данных (т.е. пакетирование параметров функций и возврат значений для
передачи их через сеть). Недостаток технологии RPC состоит в том, что она
поддерживает ограниченный набор простых типов данных. Следовательно, RPC не
подходит для передачи и возврата объектов Java. Другой недостаток RPC заключается
в том, что программисту необходимо знать специальный язык определения
интерфейса (IDL) для описания функций, которые допускают удаленный вызов.
RMI представляет собой реализацию RPC на Java для распределенных
коммуникационных взаимодействий Java-объект — Java-объект. После того как объект
Java зарегистрирован для удаленного доступа (т.е. он является удаленным объек
том), клиент может получать удаленную ссылку на этот объект, которая дает
возможность клиенту использовать этот объект дистанционно. Синтаксис вызова
метода идентичен синтаксису вызова методов других объектов в той же программе.
Удаленный вызов методов 31
Как и RPC, RMI обслуживает маршаланг данных через сеть. Однако RMI также
дает возможность программам на Java передавать законченные объекты Java с
помощью механизма сериализации объектов Java. Программисту не нужно
заботиться о передаче данных через сеть. RMI не требует от программиста знания языка
IDL, поскольку в составе J2SE имеются инструментальные средства для создания
необходимого кода для сетевых взаимодействий из определений интерфейсов
программы. Кроме того, поскольку RMI поддерживает только Java, никакого
нейтрального к языку ГО L-интерфейса не требуется; достаточно собственных
интерфейсов Java.
Мы представим два примера использования RMI и обсудим ключевые
концепции RMI по мере того, как они будут нам встречаться в ходе рассмотрения
примеров. Изучив эти примеры, вы сможете лучше понять модель сетевого
взаимодействия с помощью RMI и воспользоваться преимуществами средств RMI для
построения распределенных приложений Java — Java.
[Замечание. Для коммуникационных взаимодействий Java — не-Java вы
можете использовать язык Java IDL (введенный в Java 1.2} или RMl-ПОР. Java IDL
и RMI-IIOP дают возможность приложениям и апплетам, написанным на Java,
взаимодействовать с объектами, написанными на других языках
программирования, которые поддерживают архитектуру CORBA.]
2.2. Практический пример. Создание распределенной
системы с помощью RMI
В последующих нескольких разделах мы представим пример, использующий
RMI, в котором загружается информация о прогнозе погоды для
путешественников Traveler's Forecast с Web-сайта Национальной службы погоды:
http://itrin.nwa.noaa.gov/iwin/ui/traveler.html
[Замечание. Во время разработки программного обеспечения этого примера,
формат Web-страницы Traveler's Forecast менялся несколько раз (типичная ситуация
для современных динамичных Web-страниц}. Используемая н этом примере
информация напрямую зависит от формата Web-страницы Traveler's Forecast. Бели
у вас возникнет проблема с выполнением данного примера, обратитесь к странице
часто задаваемых вопросов на нашем Web-сайте www.deitel.com.]
Мы храним информацию прогноза погоды Traveler's Forecast в удаленном
объекте RMI, который принимает запросы на информацию о погоде посредством
удаленных вызовов методов.
В примере выполняются четыре основных действия:
1. Определение удаленного интерфейса с объявлениями методов, которые
клиент может вызвать у удаленного объекта.
2. Определение реализации удаленного объекта для удаленного интерфейса.
[Замечание. В соответствии с соглашением, класс реализации удаленного
объекта имеет то же имя, что и удаленный интерфейс, но заканчивается на
Impl.]
3. Определение клиентского приложения, которое использует удаленную
ссылку, чтобы взаимодействовать с реализацией интерфейса (т.е. объектом
класса, который реализует удаленный интерфейс).
4. Компиляция и выполнение удаленного объекта и клиента.
32 Глава 2
2.3. Определение удаленного интерфейса
Первый этап при создании распределенного приложения с помощью RMI
состоит в определении удаленного интерфейса, который описывает удаленные методы,
посредством которых клиент взаимодействует с удаленным объектом, используя
RMI. Чтобы создать уделенный интерфейс, определите интерфейс, который
расширяет интерфейс java.rmi.Remote. Интерфейс Remote представляет собой
тегирующий интерфейс — он не объявляет каких-либо методов и, следовательно, не
обременен реализацией класса. Объект класса, который реализует интерфейс
Remote прямо или косвенно, представляет собой удаленный объект и может быть
доступен — с соблюдением соответствующих полномочий, определяемых системой
безопасности, — из любой виртуальной машины Java, которая имеет соединение
с компьютером, на котором выполняется удаленный объект.
® Общая методическая рекомендация 2.1
Удаленный метод должен быть объявлен в интерфейсе, который расширя
ет интерфейс java.rmi.Remote.
® Общая методическая рекомендация 2.2
Распределенное RMI-приложение должно экспортировать объект класса,
который реализует интерфейс Remote, чтобы сделать этот удаленный
объект доступным для приема удаленных вызовов методов.
Интерфейс Weather Service (рис. 2.1}, который расширяет интерфейс Remote
(строка 10), представляет собой интерфейс для нашего удаленного объекта. В
строке 13 объявляется метод getWeatherlnformation, который клиент может вызвать
для получения информации о погоде от удаленного объекта. Заметим, что хотя
удаленный интерфейс WeatherService определяет только один метод, в принпипе
удаленные интерфейсы могут объявлять несколько методов. Удаленный объект
должен реализовывать все методы, объявленные в его удаленном интерфейсе.
1 // WeatherService.Java
2 // Интерфейс WeatherService обтяаляет метод для получения
3 // информации о погоде.
4 package com.deitel.«dvjhtpl.rmi.weather;
5
6 // Набор бааових пакетов Java
7 import java.rmi.*;
8 import java.ufcil.*;
9
10 public interface HeatherService extends Remote {
11
12 // получение вектора объектов WeatherBean от сервера
13 public List getWeatherlnformation() throws RamoteException;
14
15 }
Рис. 2.1. Интерфейс WeatherService
Когда компьютеры взаимодействуют между собой по сети, есть вероятность
возникновения проблем при таких взаимодействиях. Например, компьютер сервера
может выйти из строя, или же может отказать какой-либо сетевой ресурс. Бели
подобная проблема возникает в процессе удаленного вызова метода, удаленный
метод возбуждает исключение RemoteException, которое является контролируемым
исключением.
Удаленный вызов методов
33
Ш Общая методическая рекомендация 2.3
Каждый метод в интерфейсе Remote должен содержать throws для
указания, что метод может возбуждать исключения RemoteException.
Ш Общая методическая рекомендация 2.4
RM1 использует механизм сериализации по умолчанию Java для передачи
параметров методу и возврата значений через сеть. В этой связи все
параметры метода и возвращаемые значения должны иметь описатель
Serializable или один из примитивных типов.
2.4. Реализация удаленного интерфейса
Следующим этапом является определение реализации удаленного объекта.
Класс WeatherServicelmpl (рис. 2.2) представляет собой удаленный объект,
который реализует удаленный интерфейс WeatherService. Клиент взаимодействует
с объектом класса WeatherServicelmpl, вызывая метод get Weather Information
интерфейса WeatherService для получения информации о погоде. Класс
WeatherServicelmpl хранит сведения о погоде в списке (List) объектов WeatherBean
(рис. 2.3}. Когда клиент вызывает удаленный метод getWeatherlnformation,
объект WeatherServicelmpl возвращает ссылку на список List объектов Weather-
Bean. RMI возвращает сериализованную копию списка List клиенту. RMI затем
осуществляет десериализацию списка List на принимающей стороне и
предоставляет вызвавшему процессу ссылку на объект List.
1 // WeatherServicelmpl.Java
2 // WeatherServicelmpl реализует удаленный интерфейс WeatherService
3 // для предоставления удаленного объекта WeatherService.
4 package com.deitel.advjhtpl.mi.weather;
5
6 // Набор базовых пакетов Java
7 import java.io.*;
8 import Java.net.URL;
9 import java.rmi.*;
10 import Java.mi.server.*;
11 import ^ava.util.*;
12
13 public class WeatherServicelmpl extends UnicaatRemot«Object
14 implements HeatherService (
15
16 private List weatherlnformation; // список объектов WeatherBean
17
18 // инициализация сервера
19 public WeatherServicelmpl() throws RemoteException
20 (
21 super(>;
22 updateWeatherCondifcic-ns();
23 >
24
25 // получение информации о погоде с сайта NWS
26 private void updateWeatherConditions()
27 {
28 try (
System.err.println( "Update weather information..." );
// страница Travelers Forecast Национальной службы погод»
URL url = пей ORL(
"http://ivin.nwa.noaa.gov/iwin/us/traveler.html" );
// настройка текстового потока ввода для чтения
// содержимого Web-страницы
BufferedReader in = new BufferedReader(
new InputStreamReader( url.openStreamQ ) );
// рваделитель, указывающий начало данных на Web-странице
String separator = "TAV12";
// нахождение разделителя на Web-странице
while ( >in.readLine(}.3tartsWith( separator ) )
; // ничего не предпринимать
// строки, представлнищие заголовки на Web-странице
// Travelers Forecast с информацией о дневкой и ночной
погоде
String dayHeader =
"CITY WEA HI/LO BEA HI/LO";
String nightHeader =
"CITY HEA LO/HI BEA LO/BT";
String inputLine = "";
// нахождеине заголовка, с которого начинается
// информация о погоде
do (
inputLine = in. readLine () ;
} while ( !inputLine„equals( dayHeadar } £&
!inputLine.equals( nightHeader ) );
weatherlnformation = new ArrayList(); // созд. списка List
// создание объектов WeatherBean, содержащих данные
// о погоде, и сохранение их в векторе weatherlnformation
inputLine = in.readLineО; // получение информации для
// первого города
// Часть строки ввода inputLine, содержащая нужные данные,
// имеет длину в 28 символов. Если длина строки
// превышает 28 символов, выполнить обработку данных.
while ( inputLine.length() > 28 ) {
// Создание объекта WeatherBean для города. Первые 16
// символов относятся к названию города. Далее, 6 сим-
// волов является описанием погод». Следующие 6 символов
// относятся к макс./ния. или мин./мекс. температуре.
WeatherBean weather = new MeatherBean(
inputLine.substring! 0, 16 ),
inputLine.substring! ^6, 22 ),
inputLine.substring) 23, 29 ) );
// добавление объекта WeatherBean в список List
Удаленный вызов методов 35
82 weatherInformation.add( weather );
83
84 inputLine = in.readLine(); // получение информации
// о оледужщем городе
85 J
86
87 in. close (}'' // закрытие соединения с Web-сервером
88
89 System.err.println( "Weather information updated." );
90
91 J
92
93 // обработка ошибки при соединении с сервером HWS
94 catch( java.net.СоплесtExcaption connactException ) (
95 connectException.printstaсkTrace();
96 System.exit( 1 );
97 J
98
99 // обработка других исключений
100 catch( Exception exception ) (
101 exception.printStackTrace(};
102 System.exit( 1 );
103 }
104 )
105
106 // реализация метода интерфейса HeatherService
107 public List getWeatherlnformation() throws RemoteException
108 {
109 return weatherInformation;
110 }
111
112 // запуск удаленного объекта Weatherservica
113 public static void main( String args[} ) throws Exception
114 (
115 System.err.println( "Initializing WeatherService..." );
116
117 // создание удаленного объекта
118 WeatherService service = new WeatherServicelmpl()r
119
120 // задание инеин удаяениого объекта
121 String servexObjectName = "rmirZ/localhoet/WeatherService";
122 // регистрация удахенного объекта WeatherService в реестре
123 // noi regis try
124 Naming.rebind( serverObjectName, service );
125
126 System.err.println( "WeatherService running." };
127 )
128 } _
Рис. 2.2. Класс WeatherServicelmpl реализует удаленный интерфейс WeatherService
Национальная служба погоды National Weather Service обновляет
Web-страницу, из которой мы извлекаем информацию, два раза в день. Однако класс
WeatherServicelmpl загружает эту информацию только один раз, когда
запускается сервер. В упражнениях к главе вам будет предложено модифицировать
сервер, чтобы обновлять данные два раза в день. [Замечание. Класс Weather-
36 Глава 2
Service Imp 1 чувствителен к изменениям формата Web-страницы сайта Traveler's
Forecast службы National Weather Service. Если у вас возникнут проблемы при
выполнении этого упражнения, обратитесь к странице вопросов и ответов на нашем
Web-сайте www.daitel.cont.]
Класс WeatherServicelmpl расширяет класс UnicastRemoteObject (пакет Java,
rmi.server} и реализует удаленный интерфейс WeatherService (строки 13-14}.
Класс UnicastRemoteObject предоставляет базовые функциональные
возможности, необходимые для всех удаленных объектов. В частности, конструктор
экспортирует объект, чтобы сделать его доступным для приема удаленных вызовов.
Экспорт объекта дает возможность удаленному объекту ожидать соединений с
клиентами на анонимном порту (т.е. порту, выбираемом компьютером, на котором
выполняется удаленный объект}. Это дает возможность объекту осуществлять
однонаправленное взаимодействие (взаимодействие точка-точка между двумя
объектами посредством вызовов методов) с использованием стандартных соединений
через со кеты. RMI абстрагируется от деталей этих взаимодействий, поэтому
программист может работать, применяя простые вызовы методов. Конструктор
WeatherS ervicelmpl (строки 19-23) вызывает конструктор по умолчанию для класса
UnicastRemoteObject (строка 21) и вызывает private метод apdateWeatherCon-
ditions (строка 22). Перегруженный конструктор для класса UnicastRemoteObject
дает возможность программисту задавать дополнительную информацию, такую
как номер порта для экспорта удаленного объекта. Все конструкторы
UnicastRemoteObject возбуждают исключения RemoteException.
S Общая методическая рекомендация 2.5
Конструкторы и методы класса UnicastRemoteObject возбуждают
контролируемое исключение RemoteException, поэтому подклассы класса
UnicastRemoteObject должны определять конструкторы, которые также
возбуждают исключение RemoteException.
Ш Общая методическая рекомендация 2.6
Класс UnicastRemoteObject предоставляет базовые функциональные
возможности, которые необходимы удаленным объектам для обслуживания
удаленных запросов. Классам удаленных объектов не нужно расширять
этот класс, если эти классы используют статический метод export-
Object класса UnicastRemoteObject для экспорта удаленных объектов.
Метод updateWeatherConditions (строки 26-91} читает информацию о погоде
с Web-страницы Traveler's Forecast и сохраняет эту информацию в списке List
объектов WeatherBean. В строках 32-33 создается объект URL для Web-страницы
Traveler's Forecast. В строках 36-37 вызывается метод openStream класса URL
для открытия соединения с заданным URL и «упаковки» этого соединения в
объект BufferedRead.
В строках 40-87 осуществляется извлечение информации о погоде из HTML-
страницы. В строке 40 определяется строковый разделитель — "TAV12" —
который определяет начальную точку, с которой начинается поиск соответствующей
информации о погоде. В строках 43-44 осуществляется чтение о Web-страницы
Traveler's Forecast, пока не будет достигнут разделитель. Это позволяет
пропускать информацию, не нужную для данного приложения.
В строках 48-51 определены две строки, которые представляют заголовки
столбцов для информации о погоде. В зависимости от времени дня, заголовки
столбцов будут иметь вид
"CITY HEA HI/LO WEA HI/LO"
Удаленный вызов методов 37
после утреннего обновления (обычно около 10.30 по стандартному восточному
времени) или
"CITY ИЕА LO/HI HEA LO/BI"
после вечернего обновления (обычно около 22.30 по стандартному восточному
времени).
В строках 65-85 осуществляется чтение информации о погоде в каждом городе
и помещение этой информации в объекты We at her Bean. Каждый объект Wea-
therBean содержит название города, температуру и описание погоды. В строке 61
создается список List для хранения объектов WeatherBean. В строках 76-79
формируется объект WeatherBean для текущего города. Первые 16 символов в строке
inpntLine относятся к названию города, следующие 6 символов описывают погоду
(т.е. содержат прогноз), а следующие 6 символов представляют верхний и нижний
предел температуры. Последние два столбца данных относятся к прогнозу погоды
на следующий день и в этом примере игнорируются. В строке 82 объект
WeatherBean добавляется в список List. В строке 87 объект BufferedRead и связанный
с ним поток ввода InpntStream закрывается.
Метод get We atberin formation (строки 107-110) представляет собой метод
интерфейса WeatherService, который класс WeatherServicelmpI должен реализовы-
вать, чтобы отвечать на удаленные запросы. Метод возвращает сериализованную
копию списка weatherlnformation (объект класса List). Клиенты вызывают этот
удаленный метод, чтобы получить информацию о погоде.
Метод main (строки 113-127) создает удаленный объект W eatherSer vice Imp].
Когда конструктор выполняется, он экспортирует удаленный объект, чтобы
прослушивать удаленные запросы. В строке 106 определяется URL, который клиент
может использовать для получения удаленной ссылки на объект. Клиент
использует эту удаленную ссылку для вызова методов удаленного объекта. URL обычно
имеет форму
rmi://хост;порт/ИмяУдалрнногоОбъехта
где хост представляет собой имя компьютера, который выполняет реестр для уда
лепных объектов (он также является компьютером, на котором выполняется
удаленный объект), порт представляет собой номер порта, через который
выполняется реестр на хост-компьютере, а ИмяУдаленногоОбъекта — имя, которое клиент
будет предоставлять при попытках обнаружить удаленный объект в реестре.
Утилита rmiregistry обслуживает реестр удаленных объектов и является составной
частью J2SE. Номер порта реестра RMI по умолчанию — 1099.
Ш Общая методическая рекомендация 2.7
Предполагается, что клиенты RMI должны осуществлять соединение на
порту 1099 при попытке найти удаленный объект в реестре RMI (если
в URL для удаленного объекта явным образом не задан другой номер порта).
Ш Общая методическая рекомендация 2.8
Клиент должен задавать номер порта только в том случае, если реестр
RMI выполняется на порту, отличном от порта 1099, принятого по
умолчанию.
В этой программе TJRL удаленного объекта имеет вид
rmi: //localhost/Weather3ervi.ee
Из этого следует, что реестр RMI выполняется на машине local host (т.е. на
локальном компьютере), а для обнаружения клиентом сервиса должно использоваться
имя WeatherService. Имя localhoet является синонимом IP-адреса 127.0.0.1,
поэтому предыдущий URL эквивалентен
38
Глава 2
rmi : //127.0.0.1/НеatherService
В строке 124 вызывается статический метод rebind класса Naming (пакет
java.rmi) для связывания удаленного объекта service класса WeatherServicelmpI
в реестре RMI с URL rtm://Iocalhost/Weather Service. Для связывания удаленного
объекта с реестром также используется метод bind. Программисты чаще
используют метод rebind, поскольку он гарантирует, что если объект уже был
зарегистрирован под заданным именем, новый удаленный объект заменит ранее
зарегистрированный объект. Это может быть важно, если регистрируется новая версия
существующего удаленного объекта.
Класс WeatherBean (рис. 2.3) хранит данные, которые класс Weather
ServicelmpI извлекает С Web-сайта National Weather Service. Этот класс хранит город,
температуру и описание погоды в виде строк. В строках 64-85 предоставлены
методы get для каждого фрагмента информации. В строках 25-45 загружается файл
свойств, который содержит имена изображений для отображения информации
о погоде. Этот статический блок обеспечивает доступность имен изображений
сразу же, как только виртуальная машина загружает класс WeatherBean в память.
1 // WeatherBean.Java
2 // WeatherBean содержит информацию о погоде дли одного города.
3 package com, deitel.advjhtpl.rmi.weather;
4
5 // Набор базовых пакетов Java
в import java.awt.*;
7 import java.io.*;
8 import java.net.*;
9 import java.util.*;
10
11 // Пакеты расширений Java
12 import javan.swing.*;
13
14 public clasa WeatherBean implements Serlalisable (
15
16 private String cityName; // название города
17 private String temperature; // ■температура в городе
18 private String description; // описание погоды
19 private Imagelcon image; // изображение характера погоды
20
21 private static Properties imagetiames;
22
23 // инициализация объекта mageNames при загрузке класса
24 // WeatherInfo в память
25 static (
26 imageNames « new Properties(); // создание таблицы свойств
27
28 // загрузка описаний погоды и имен изобращений из
29 // файла свойств
30 try (
31
32 // получение URL для файла свойств
33 DRL url = WeatherBean.class.getResource(
34 "imagenames.properties" );
35
36 // загрузка содержимого файла свойств
37 imageNames.load( new FilelnputStreamJ url.getFile() ) );
Удаленный вызов методов
// обработка исключений при открытии файла
catch ( lOException ioException ) {
ioException.printStackTraceO;
} // конец блока static
// конструктор WeatherBean
public HeatherBean( String city. String veatherDescription,
String cityTemperature )
(
cityName = city;
temperature = cityTemperature;
description = waatherDeaeription.trimQ;
CTRL url = HeatherBean.class.getResource( "images/" +
imageNames.getPropertyt description, "noinfo.jpg" } ),-
// получение икеки изображения >
II noinfo.jpg, если описание породы i
image = пем ImageIcon( url );
1
// получение названия города
public String getCityNameQ
(
return cityName;
1
// получение температур»
public String getTemperature()
return tenperature;
// получение описания погоды
public String getDeacription()
return description;
// получение изображения характера погоды
public Imagelcon getlmageO
i
return image;
Рис. 2.З. Объект класса WeatherBean хранит прогноз погоды для одного города
Далее, мы определяем клиентское приложение, которое будет получать
информацию о погоде из класса WeatherServicelmpl. Класс WeatherServiceClient
(рис. 2.4) является клиентским приложением, которое вызывает удаленный метод
getWeatherlnfonnation интерфейса длн Weather Service для получения информа-
40 Глава 2
ции о погоде через RMI. Класс Weather ServiceClient использует список JList с
нестандартный интерфейсом Lis t Cell Rend егег для отображения информации о
погоде для каждого города.
1 // HeatherServiceClient.java
2 // HeatherServiceClient использует удаленный объект
3 // HeatherServj.ee дли извлечения информации о погоде.
4 package com.deitel.advjhtpl.rmi.weather;
5
6 // Набор базовых пакетов Java
7 import java.rmi.*;
8 import java.util.*;
9
10 // Пакеты расширения Java
11 import javan.swing.*;
12
13 public class HeatherServicedlent extends JFrame
14 {
15 // конструктор HeatherServiceClient
16 public HeatherServiceClient( String server }
17 {
IB superf "RHI HeetherService Client" 1;
19
20 // соединение с сервером и получение информации о погоде
21 try {
22
23 // имя удаленного объекта, связанного с реестром rmi
24 String remoteName = "rmi://" + server + "/HeatherService";
25
26 // потек уделеного объекта HeatherServicelmpl
27 WeatherService weatherService =
26 ( HeatherService ) Naming.lookup( renoteName ),
29
30 // получение информации о погоде с сервера
31 List weatherInformation = new ArrayList(
32 weatherService.getHeatherInformation() ):
33
34 // создание модели HeatherListHodel для информации о погоде
35 LiatModel weatherListModel =
36 new HeatherListModel{ weatherInformation };
37 // создание списка JList, задание ListCellRenderer
38 // и добавление в GUI
39 JList weatherJLiet = new JList( weatherListHodel );
40 weatherJLiet.setCellRenderer( new HeatherCellRenderer{) };
41 getContentPane().add( new JScrollPane( weatherJLiet ) ) ,-
42
43 ) // конец оператора try
44
45 // обработка исключений при соединения с удаленный сервером
46 catch ( ConnectException connectionException ) (
47 System.err.println( "Connection to server failed. " +
48 "Server may be temporarily unavailable." );
49
50 connectionException.printStackTrace();
51 }
Удаленный вызов методов
41
52
53 // обработка исключений при взаимодействии с удаленным сервером
54 catch ( Exception exception } {
55 exception.printstackTrасе();
56 1
57
58 } // конец конструктора WeatherServiceClient
59
60 // выполнение WeatherServiceClient
61 public static void main) String args{] )
62 (
63 WeatherServiceClient client = null;
64
65 // если IP-адрес или доменное кия сервера не заданы,
66 // использовать localhost; иначе использовать заданное ими
67 if { args.length = 0 )
68 client = new WeatherServiceClient( "localhost" );
69 else
70 client = new WeatherServiceClient ( axgsf 0 ] ) ,-
71
72 // отображение окна приложения
73 client.setDefaultCloseOperationt JFrame.EXIT_ON_CLOSE 1;
74 client.packQ ;
75 client.setResizable( false );
76 client.setviaible{ true );
77 1
78 >
Рис. 2.4. Клиентское приложение WeatherServiceClient для удаленного объекта
WeatherService
Конструктор WeatherServiceClient (строки 16-58) принимает в качестве
параметра имя компьютера, на котором выполняется удаленный объект Weather-
Service. В строке 24 создается строка (String), которая содержит URL для этого
удаленного объекта. В строках 27-28 вызывается статический метод lookup
класса Naming для получения удаленной ссылки на удаленный объект WeatherService
с заданным UKL. Метод lookup осуществляет соединение с реестром RMI и
возвращает удаленную ссылку на удаленный объект, поэтому в строке 28 осуществляется
приведение этой ссылки к типу WeatherService. Имейте в виду, что класс
WeatherServiceClient обращается к удаленному объекту только через интерфейс
WeatherService — удаленный интерфейс для реализации удаленного объекта
WeatherServicelmpl. Клиент может использовать эту удаленную ссылку, если она
обращается к локальному объекту, выполняющемуся на той же виртуальной
машине. Эта удаленная ссылка обращается к объекту-заглушке на клиенте.
Заглушки дают возможность клиентам вызывать методы удаленного объекта.
Объекты-заглушки принимают удаленные вызовы метода и передают эти вызовы RMI,
который выполняет сетевые соединения, позволяющие клиентам
взаимодействовать с удаленным объектом. В данном случае заглушка WeatherServicelmpl будет
обслуживать взаимодействие между объектами WeatherServiceClient и
WeatherServicelmpl. Уровень RMI отвечает за сетевые соединения с удаленными
объектами, поэтому обращения к удаленным объектам являются прозрачными для
пользователя. RMI обслуживает соединение с удаленным объектом, передачу
параметров и возврат значений.
42
Глава 2
В строках 31-32 вызывается удаленный метод get Weather Information no
удаленной ссылке weatherService. Этот вызов метода возвращает копию списка List
объектов WeatherBean, которые содержат информацию с Web-страннцы Traveler's
Forecast. Важно учитывать, что RMI возвращает копию списка List, поскольку
возврат ссылки при удаленном вызове метода отличается от возврата ссылки при
локальном вызове метода. RMI использует сериалнзацию объектов для отправки
списка List объектов WeatherBean клиенту. Следовательно, параметр и возвращаемые
типы для удаленных методов должны иметь описатель Serializable,
В строках 35-36 создается модель WeatherListModel (рис. 2.5),
способствующая отображению информации о погоде в списке JList (строка 39). В строке 40
устанавливается компонент ListCellRenderer для списка JList. Класс Weatber-
CellRenderer (рис. 2.6) является компонентом для отображения ListCellRenderer,
который использует объекты класса Weatherltem для отображения информации
о погоде, хранящейся в объектах WeatherBean.
1 // WeatherListModel.java
2 // WeatherListModel расширяет класс JtostractListModel
3 // для хравеямя списка объектов WeatherBeans-
4 package coo.deitel.advjhtpl.rmi.weather;
5
6// Набор базовых пакетом Java
7 import java.util.*;
8
9 // Пакеты расширений Java
10 import javax.swing.AbstractLietModel;
11
12 public class WeatherListModel extends AbstractListModel {
13
14 // Список влементоа я модели ListModel
15 private List liet;
16
17 // конструктор без параметров WeatherListModel
18 public WeatherListModel()
19 (
20 // создание нового списка для объектов WeatherBean
21 list - new ArrayListO ;
22 }
23
24 // конструктор WeatherListModel
25 public weatherLietModal( List itemLiet )
26 (
27 list = itemList;
28 }
29
30 // получение раамера списка
31 public int getSize()
32 (
33 return list.size О;
34 )
35
36 // получение ссыпки типа Object ка влемеят с заданным индексом
37 public Object getElamentAt( int index )
Зв {
39 return list.get( index );
40 J
41
нныи вызов методов
// добавление элемента а модель HeatherLietModel
public void add( Object element }
(
list.add( element );
firelntervalAdded( this, list. size() , liet.size() ) ;
// удаление элемента из модени WeatherListModel
public void remove( Object element )
(
int index = list.indexOf( element );
if ( index '= -1 ) {
list.remove( element );
firelntervalRemovedf this, index, index );
) // конец метода remove
// удаление всех элементов иа модели HeatherListModel
public void clear()
// очистка всех элементов в списке
list.clear(};
// уведомление схушателей об изменении содержимого
£ireContent«Changed{ this, 0, size );
Рис. 2.5, Класс WeatherListModel является реализацией интерфейса ListModel для
хранения информации о погоде
Метод main (строки 61-77) проверяет параметры командной строки для имени
компьютера, введенного пользователем. Бели пользователь не предоставил имя
компьютера, в строке 68 создается новый объект WeatherServiceClient, который
соединяется с реестром RMI, выполняющемся на localhost. Бели пользователь
предоставил имя компьютера, в строке 70 создается объект WeatherServiceClient
с использованием заданного имени компьютера.
Класс WeatherListModel (рис. 2.5) представляет собой модель типа ListModel,
которая содержит объекты WeatherBean, отображаемые в списке JList. В этом
примере используется паттерн проектирования Adapter, который дает
возможность взаимодействовать друг с другом компонентам, имеющим несовместимые
интерфейсы.1 Паттерн проектирования Adapter имеет множество параллелей в
реальном мире. Например, электрические вилки для бытовых электроприборов
в США не совместимы с электрическими розетками, применяемыми в Европе.
Чтобы использовать американские электроприборы в Европе, пользователю необ-
1 Gamma, Erich, Richard Helm, Ralph Johnson and John VHssides. Design Patterns;
Elements of Reusable Object Oriented Software. {Reading, MA: Addison-Wesley, 1995),
p. 139. Русский перевод: Э. Гамма. Р. Хелм, Р. Джонсон, Дж. Влиссидес. »Приемы
объекты о-ориентированного программирования. Паттерны проектирования». СПб.:
Питер, 2001, с. 141.
Глава 2
ходимо использовать адаптер между электрической розеткой и вилкой. С одной
стороны, этот адаптер обеспечивает интерфейс, совместимый с американской
электрической вилкой. С другой стороны, адаптер обеспечивает интерфейс,
совместимый с европейской электрической розеткой. Класс WeatherListModel играет роль
адаптера в шаблоне конструирования Adapter. В Java интерфейс List не является
совместимым с интерфейсом класса JList — JList иожет извлекать элементы
только на модели List Model. В связи с этим мы представляем класс WeatherList Model,
который адаптирует интерфейс List к интерфейсу JList. Когда JList вызывает
метод get Size класса WeatherList Model, объект WeatherList Model вызывает метод
size интерфейса List. Когда объект JList вызывает метод get Element At класса
WeatherListModel, модель WeatherList Model вызывает метод get класса JList
и т.д. Класс Weather List Model также играет роль модели в архитектуре
делегат-модель интерфейса Swing, о чем мы говорили в главе 3 первой части книги.
Класс JList использует интерфейс List Cell Renderer для отображения
элементов в модели List Model объекта JList. Класс WeatherCellRenderer (рис. 2.6)
является подклассом класса DefaultListCe 11 Renderer для воспроизведения объектов
WeatherBean в списке JList. Метод get List Cell RendererComponent создает и
возвращает объект Weatherltem (рис. 2.7) для заданного объекта WeatherEean.
1 // WeatherCellRenderer.Java
2 // НеаtherCe11Renderer - спепианкэированннй интерфейс
3 // ListealIRanderer для объектов WeatherBean в списке JList.
4 package com.deitel.advjhtpl.rmi.weather;
5
6 // Набор базовых пакетов Java
7 import java.awt.*;
В
9 // Пакеты расширений Java
10 Import javax.siting. *;
11
12 public claas HeatherCellRenderer extends DefaultListCellRendecer (
13 // возврат объекта Weatherltem, который отображает клформацию
14 //о породе а городе
15 public Component getLiatCe11 RendererComponent( JList list,
16 Object value, int Index, boolean iaSelected, boolean focus )
17 <
18 return new Weatherltem( ( WeatherBean ) value );
19 )
20 ) '
Рис. 2.6. Класс WeatherCellRenderer является видоизмененным классом UstCellRenderer
для отображения объектов WeatherBean в списке JList
Класс Weatherltem (рис. 2.7) является подклассом класса J Panel для
отображения информации о погоде, хранящейся в объекте WeatherBean. Класс
WeatherCellRenderer использует экземпляры класса Weatherltem для отображения
информации о погоде в списке JList. Статический блок (строки 22-29) загружает
объект backgronn dlmage класса Imagelcon в нанять, когда виртуальная машина
загружает сам класс Weatherltem. Тем самым обеспечивается, что объект back-
groundlmage будет доступен всем экземплярам класса Weatherltem. Метод paint-
Component (строки 38-56) отображает изображение backgronndlmage (строка 43),
название города (строка 50), температуру (строка 51) и изображение Imagelcon
объекта WeatherBean, которое описывает погодные условия (строка 54).
Удаленный вызов методов 45
1 // Weatherltem.Java
2 // HeatherItem отображает информацию о погода в панени JPanel
3 package com.deitel.advjhtpl.rmi.weather;
4
5 // Набор базовых пакетов Java
6 import java.ewt.*;
7 import java.net.*;
в import java.util. *,-
9
10 // Пакеты расширений Java
11 import javaat.swing.*;
12
13 public class Weatherltem extends JPanel {
15 private WeatherBean weatherBean; // информация о погоде
16
17 11 фоновый рисунок ImageIcon
18 private static ImageIcon backgroundlmage;
19
20 // блок инициализации загружает файл изображения,
21 // когда класс Weatherltem загружается в память
22 static {
23
24 // получение DRL для фонового изображения
25 URL url = Weatherltem.class.getResource( "images/back.jpg" );
26 // фоновое изображения для информации о погоде
27 // в каждом из городов
28 backgroundlmage = new ImageIcon( url );
29 )
30
31 // инициализации Weatherltem
32 public Weatherltem( WeatherBean bean )
33 {
3d weatherBean = bean;
35 1
36
37 // отображение киформации о погоде в городе
38 public void paintComponent( Graphics g )
39 {
40 super. paintComponent ( g ) ,-
41
42 // отображение фома
43 backgroundlmage.paintXcon( this, g, 0, 0 );
44
45 // задание шрифта и цвета рисования,
46 // затем отображение названия города и температуры
47 Font font = new Font( "SaneSerif", Font.BOLD, 12 );
48 g.setFont( font );
49 g.setColor( Color.white );
50 g.drawStringt weatherBean.getCityHeme(), 10, 19 );
51 g.drawStringt weatherBean.getTemperatuxa() , 1-30, 19 );
52
53 // вывод изображения для характера погоды
54 weatherBean.getlmage().paintlcont this, g, 253, 1 );
55
56 } // конец метода paintComponent
46 Глава 2
57
58 // sад«яме предпочтительных раамеров охме Heather Item,
59 // р&виия высота и ширине фонового изображения
60 public Dimension getPreferredSize()
61 {
62 return new Dimension( backgroundImage.getlconHidth(),
63 backgroundlmagegetlconHeightO );
64 )
65 }
Рис. 2.7. Класс Weath*rltem отображает информацию о погоде для одного города
Изображения для этого примера вместе с текстом кода имеются на нашем
Web-сайте (www.deitel.com). Щелкните на ссылке Downloads и скопируйте
примеры для книги Advanced Java 2 Platform How to Program.
2.5. Компиляция и выполнение сервера и клиента
Подготовив отдельные фрагменты, мы можем сформировать и выполнить наше
распределенное приложение, но для этого потребуется несколько действий.
Сначала мы должны компилировать классы. Далее, мы должны компилировать класс
удаленного объекта (WeatherServicelmpi), используя компилятор rmic (утилита
J2SE) для формирования класса-заглушки. Как уже говорилось в разделе 2.4,
класс-заглушка переадресует вызовы методов на уровень RM1, который
осуществляет необходимые сетевые взаимодействия для активизации вызова метода
удаленного объекта. Командная строка
rmic -vl.2 con.deitel.advjhtpl.rmi.weather.HeatherServicelmpi
генерирует файл WeatherServiceImpl_S tub .class. Этот класс должен быть доступен
для клиента (либо локально, либо путем загрузки по сети), чтобы дать возможность
устанавливать удаленное соединение с серверным объектом. В зависимости от
параметров командной строки, передаваемых rmic, может быть сгенерировано
несколько файлов. В Java 1.1 rmic формирует два класса — класс-заглушку и класс-каркас
(skeleton). В Java 2 класс-каркас больше не требуется. Параметр командной строки
—vl.2 указывает, что rmic следует создать только класс-заглушку.
Следующий этеп — запустить реестр RMI, который зарегистрирует объект
Weather Servicelmpi. Командная строка
rmiregietry
запускает реестр RMI иа локальной машине. В окне командной строки (рис. 2.8)
какой-либо текст в ответ иа эту команду отображаться не будет.
be* Типичная ошибка программирования 2.1
[Шт] Если не запустить реестр RMI, прежде чем попытаться привязать уда
^ ленный объект к реестру, будет сгенерировано исключение java.rmi.Con-
nectException, которое указывает, что программа не может соединиться
с реестром.
Рис. 2.8. Выполнение команды rmlregirtry
Удаленный вызов методов
47
Чтобы удаленный объект мог принимать удаленные вызовы методов, мы
связываем объект с именем в реестре RMI. Запустите приложение WeatherServicelmpI
из командной строки следующим образом:
Java coa.deitel.advjhtpl.rmi.weather.HeatherServicelnqpl
На рис. 2.9 показан результат выполнения приложения WeatherServicelmpI.
Класс WeatherServicelmpI получает данные с Web-страницы Traveler's Forecast
и отображает сообщение, указывающее, что сервис выполняется.
Рис. 2.9. Выполнение удаленного объекта WeatherServicelmpI
Программа WeatherServiceClient теперь может соединяться с объектом
WeatherServicelmpI, выполняющемся на локальной машине local host, с помощью
команды
Java com.deitel.advjhtpl.rmi.weather.WeatherServiceClient
На рис. 2.10 показано окно приложения WeatherServiceClient. При выполнении
программы WeatherServiceClient соединяется с удаленным серверным объектом
и отображает текущую информацию о погоде.
Если WeatherServicelmpI выполняется не на клиенте, можно указать IP-адрес
или доменное имя компьютера-сер вера в качестве параметра командной строки
при выполнении клиента. Например, чтобы осуществить доступ к компьютеру
с IP-адресом 192.168.0-150, введите команду
Java com.deltel.advjhtpl.rmi.weather.WeatherServiceClient
192.168.0.150
В первой части этой главы мы построили распределенную систему, которая
демонстрирует основы применения RMI. В последующем практическом примере мы
построим более сложную распределенную систему RMI и воспользуемся
некоторыми дополнительными функциями RMI.
Рис. 2.10. Окно приложения WeatherServiceClient
46
Глава 2
2.6. Практический пример. Приложение Deitel Messenger
с активируемым сервером
В этом разделе мы представляем практический пример, который реализует
систему интерактивного общения (чат) с помощью RMI и активируемого сервера
интерактивного общения. Рассматриваемое в качестве примера приложение Deitel
Messenger использует несколько дополнительных функциональных возможностей
RMI и модульную архитектуру, способствующую повторному использованию
разработанного программного обеспечения. В таблице на рис. 2.11 приведены классы
и интерфейсы, входящие в состав приложения, а также дано краткое описание
каждого из интерфейсов (интерфейсы выделены курсивом).
Стандартные объекты RMI, экспортированные как объекты класса Unicast-
RemoteObject, должны постоянно выполняться на сервере, чтобы обслуживать
клиентские запросы. Объекты RMI, которые расширяют класс Java.rmi.activa-
t ion. Active table, способны активироваться т.е. начинать выполняться, когда
клиент вызывает один из методов удаленного объекта. При этом экономятся
ресурсы на сервере, поскольку процессы удаленного объекта могут переводиться в
состояние ожидания и освобождать память при отсутствии клиентов, использующих
данный конкретный удаленный объект. Демон активации RMI (rmid)
представляет собой серверный процесс, который дает возможность активируемым
удаленным объектам переходить в активное состояние, когда клиент вызывает
удаленные методы этих объектов.
Имя
ChatSarver
StoppablmCba fcServer
ChatServerlmpl
ChatServer-
Administrator
ChatCIlent
ChatMes3age
MasaageManagar
RMIMe s sageManager
Мез sa geli я ten er
D± scannecttiя tenar
ClientGUI
De italMes sengax
Роль
Удаленный интерфейс, через который клиенты регистрируются для
участия а чате, покидают чат и размещают сообщения в чате.
Административный удаленный интерфейс для завершения работы
сервера интерактивного общения.
Реализация удаленного интерфейса ChatServer, которая
предоставляет сервер интерактивного общения на базе RMI
Утилита для запуска и завершения работы активируемого
интерфейса Chats erver.
Удаленный интерфейс, через который сервер Chat Server
взаимодействует с клиентами
Объект класса SerialIzable для передачи сообщений между
сервером ChatServer и клиентами ChatOlent
Интерфейс, который определяет методы для управления
коммуникационным взаимодействием между пользовательским
интерфейсом клиента и сервером ChatServer
Реализация интерфейсов ChatOlent и MessageManager для
управления коммуникационным взаимодействием между клиентом
и сервером ChatServer.
Интерфейс для классов, которые принимаю! новые сообщения
Интерфейс для классов, которые принимают уведомления при
разрыве соединения с сервером.
Пользовательский интерфейс для отправки и получения
сообщений С использованием интерфейса MessageManager
Инициатор запуска приложения для клиента Deitel Messenger
Рис. 2.11. Компоненты приложения DeitelMessenger
Удаленный вызов методов
49
Активируемые удаленные объекты устойчивы к сбоям на сервере, поскольку
удаленные ссылки на активируемые объекты являются постоянно
действующими, — когда сервер перезапускается, демон активизации RMI сохраняет
удаленную ссылку, поэтому клиенты могут продолжать использовать удаленный объект.
Подробности реализации активируемых удаленных объектов мы обсудим позднее,
когда будем рассматривать реализацию сервера интерактивного общения.
2.6.1. Активируемый сервер приложения Deitel Messenger
Как и любой другой удаленный объект RMI, удаленный объект класса Active-
table должен реализовыватъ удаленный интерфейс. Интерфейс Chat Server
(рис. 2.12) является удаленным интерфейсом для сервера Deitel Messenger.
Клиенты взаимодействуют с сервером Deitel Messenger через удаленный интерфейс
ChatServer. К удаленному интерфейсу Active table предъявляются те же
требования, что и к стандартным интерфейсам RMI.
1 // ChatServer.java
2 // ChatServer - удаленный интерфейс, который определяет, как клиент
3 // регистрируется в чате, выходит из чата и помещает сообщения ш чат.
4 package com.deitel.messenger.rmi.server;
5
6 // Набор базовых пакетов Java
7 import java.rmi.*;
8
9 // Пакеты Deitel
10 import com.deitel-messenger.rmi.ChatMessaga;
11 import com.deitel.messenger.rmi.client.ChatClient;
12
13 public interface ChatServer extends Remote {
14
15 // регистрация нового клиента ChatClient сервером ChatServer
16 public void registerClient( ChatClient client )
17 throws RemoteException;
18
19 // отмена регистрации клиента ChatClient сервером ChatServer
20 public void unregisterClient( ChatClient client )
21 throws RemoteException;
22
23 // посылка нового сообщения на сервер ChatServer
24 public void poatMessage{ ChatMesaage message )
25 throws RemoteException;
26 1
Рис. 2.12. Удаленный интерфейс ChatServer для сервера интерактивного общения Deitel
Messenger
В строке 13 объявляется, что интерфейс ChatServer расширяет интерфейс
Remote, который является обязательным для всех удаленных интерфейсов RMI.
Метод registerClient (строки 16, 17) разрешает регистрацию клиента ChatClient
(рис. 2.17} сервером ChatServer для участия в сеансе интерактивного общения.
Метод registerCIient принимает в качестве параметра клиента ChatClient,
подлежащего регистрации. Интерфейс ChatClient сам по себе является удаленным
интерфейсом, поэтому и сервер, и клиент в атом приложении являются удаленными
объектами. Это дает возможность серверу взаимодействовать с клиентами, вызы-
50
Глава 2
вал удаленные методы этих клиентов. Это взаимодействие, называемое обратным
вызовом НШ, мы обсудим при рассмотрении реализации Chat Client.
Метод nnregisterCllent (строки 20-21) дает возможность клиентам
отключаться от чата. Метод postMessage дает возможность клиентам размещать новые
сообщения в чате. Метод poetMeBBage принимает в качестве параметра ссылку на
объект ChatMeseage. Объект ChatMessage (рис. 2.18) представляет собой сериализуе-
мый объект, который содержит имя отправителя и тело сообщения. Ниже мы
рассмотрим этот класс более подробно.
Серверная часть приложения Deitel Messenger состоит из программы для
управления активируемым удаленным объектом. Интерфейс StoppableChatServer (рис. 2.13)
объявляет метод stopServer. Программа, которая управляет сервером Deitel
Messenger, вызывает метод stopServer для завершения работы сервера.
1 // StoppableChatServer.Java
2 // StoppableChatServer - удаленный интерфейс, который
3 // предоставляет механизм для завершения работы чат-сервера.-
4 package com.deitel.messenger.rmi.server,-
5
6 // Набор базовых пакетов Java
1 import java.rmi.*;
В
9 public interface StoppableChatServer extends Remote {
10
11 // остало» сервера ChatServeг
12 public void stopServer() throws RemoteExceptlon;
13 )
Рис 2.13. Удаленный интерфейс StoppableChatServer для остановки удаленного объекта
Chat Server
Класс Chat Server Impl (рис. 2.14) представляет собой активируемый RMI-объ-
ект, который реализует удаленные интерфейсы Chat Server и StoppableChatServer.
В строке 23 создается множество (Set) для хранения удаленных ссылок на
зарегистрированных клиентов Chat Client. Конструктор Chat Server Impl (строки 29-34)
принимает в качестве параметров объекты ActlvationH) и MarehaUedObject. Механизм
активации RMI требует, чтобы объекты класса Activatable предоставляли этот
конструктор. Когда демон активизации активирует удаленный объект этого класса, он
вызывает этот конструктор активизации. Параметр ActivationID задает
уникальный идентификатор для удаленного объекта. Класс Marshal led Object является
классом-оберткой, который содержит сериалнзованный объект для передачи через
RMI. В данном случае параметр Marsha lledObject содержит специфичную для
приложения инициализирующую информацию, такую как имя, под которым демон
активизации регистрирует удаленный объект. В строке 33 вызывается конструктор
суперкласса для завершения активизации. Второй параметр конструктора
суперкласса (О) указывает, что демон активизации должен экспортировать объект через
анонимный порт.
1 // ChatServerimpl.Java
2 // ChatServerImpl реализует удаленный интерфейс ChatServer
3 // для чат-сервера иа баае НЮ.
4 package coat.deitel.messenger.rmi.server;
5
6 // Набор бавоных пакетов Java
7 import java.io.*;
в import java.net.*;
Удаленный вызов методов
.activation.1
9 import java.rmi.1
10 import ja1
11 import ja'
12 import java.rmi.registry.*;
13 import java.util.*;
14
15 // Пакеты Deitel
16 import com.deitel.messenger.i
17 import com.deitel.messenger.]
18
19 public class ChatServerlmpl extends Activetable
20 implements ChatServer, StoppableChatServer {
21
22 // Залание ссылок на ChatClient
23 private Set clients = new HashSetO;
24
25 // имя объекта сервера
26 private String eerverObjectHama ;
27
28 // конструктор ChatServerlmpl
29 public ChatServerlmpl( ActivationID id. MarshalledOb]act data )
30 throws RemoteException {
31
32 // регистрация активируемого объекта м »кспорт чаре*
33 super { id, 0 ) ,-
34 )
35
36 // регистрация объекта ChatServerlmpl с помощью реестра RMI.
37 public void register! String пвхНаше ) throws RemotaException,
38 IllegalArgumentExeeption, MalformedURlJixcaption
39 {
40 // прожарка наличия имени при регистрации
41 if ( rmiNaaia = null )
42 throw new IllegalArgumentException(
43 "Registration neme cannot be null" );
44
45 serverCbjactHanve = rmiNaine,-
46
47 // привязка объекта ChatServerlmpl к реестру RHI
48 try (
49
50 // создание реестра RMI
51 System.out.println( "Creating registry ..." );
52 Registry registry =
53 LocateRegiatry.createRegistryf 1099 );
54
55 // привязка объекта RHI к умалчиваемому реестру RMI
56 System.out.printing "Binding server to registry ..." );
57 registry.rebind( serverObjectName, this );
58 )
59
60 // если реестр существует, привявать к сукествукщему реестру
61 catch { RemotaException renotaException ) {
62 System.err.printing "Registry already exists. " +
63 "Binding to existing registry ..." );
64 Naming.rebind( servarObjectMame, this );
67 System.out.println{ "Server bound to registry" );
68
69 ) // конец метода register
70
71 // регистрация нового клиента ChatClient сервером ChatServer
72 public void registerClient{ ChatClieHt client )
73 throws RamoteException
74 i
75 // добавление клиента в набор ««регистрированных клиентов
76 synchronized { clients ) (
77 clients.add( client );
78 )
79
SO System.out.println( "Registered Client; " + client );
81
82 ) // конец метода ragisterCliant
83
84 // отмена регистрации клиента сервером ChatServer
85 public void unregisterClient{ ChatClient client )
86 throws RamoteException
87 {
88 // удаление клиента иэ набора зарегистрированных клиентов
89 synchronized( clients ) {
90 clients.remove( client );
91 )
92
93 System.out.printing "Unregistered Client: " + client );
94
95 ) // конец метода unregisterClient
96
97 // посылка нового сообщения на чат-сервер
98 public void postMessage{ ChatMesaage message )
99 throws RamoteException
100 i
101 Iterator iterator = null;
102
103 // получение итератора для набора зарегистрированных клиентов
104 aynchronized( clients ) {
105 iterator = new HashSet( clients ).iterator();
106 )
107
108 // отправка сообщения каждому яв клиентов ChatClient
109 while ( iterator.hasNextf) ) {
110
111 // попытка отправить сообщение клиенту
112 ChatClient client = { ChatClient ) iterator.next{);
113
114 try i
115 client.deliverMeaaage( message );
116 )
117
118 // отмена регистрации клиента в случае выдачи
119 catch( Exception exception ) (
120 System.err.println{ "Unregiotering absent client
121 unregisterClient( client );
л вызов методов
122 )
123
124 } // конец цикла while
125
126 } // конец иетода postHessage
127
128 // уведомление каждого клиента об ост&яове сервера
129 // и завершение серверного приложения
130 public void stopServer{) throws RemoteExcaption
131 i
132 System.out.printing "Terminating server ..." );
133
134 Iterator iterator = null;
135
136 // получение итератора для набора зарегистрированных клиентов
137 synchronized{ clients ) (
138 iterator = new HashSetf clients ).iterator{);
139 1
140
141 // отправка сообщения каждому клиенту ChatClient
142 while ( iterator,hasNext() ) {
143 ChatClient client = ( ChatClient ) iterator.next();
144 client.serverstoppingО;
145 )
146 // создание программного потока Thread для завершения
147 // приложения после того, как метод stopServer вовраткл
148 // управление выав&лшеыу процессу
149 Thread terminator = new Thread(
150 new RunnableO {
151 // ожидание в течение 5 секунд, вывод сообщение
152 // и завершении работы
153 public void run О
154 {
155 // ожидание
156 try (
157 Thread.sleep* 5000 );
158 )
159
160 // игнорировать исключение InterruptedExceptions
161 catch { XnterruptedException exception ) (
162 }
163
164 System.err.printlnf "Server terminated" );
165 SysteH.exit{ 0 );
166 )
167 1
168 ) ;
169
170 terminator.start(); // начать завершение потока
171
172 } // конец метода stopServer
ПЗ )
Рис. 2.14. Реализация ChatServeHmpI удаленных интерфейсов ChatServer
и StoppableChatServer активируемых удаленных объектов
Глава 2
Метод register (строки 37-69) регистрирует удаленный объект С hat Server Imp 1
в реестре RMI. Бели предоставленным именем удаленного объекта является null,
в строках 42-43 возбуждается исключение IllegalArgnment Except ion,
указывающее, что вызвавший процесс должен задать имя для удаленного объекта. В
строках 52-53 используют статический метод createRegistry класса LocateRegistry
для создания нового реестра на локальном компьютере на порту 1099, который
является портом по умолчанию. В строке 57 вызывается метод rebmd класса
Registry, чтобы связать активируемый объект с реестром Registry- Бели создание
или связывание с реестром оканчивается неудачей, мы делаем предположение, что
реестр RMI уже выполняется на локальной машине. В строке 64 вызывается
статический метод rehind класса Naming для связывания удаленного объекта с
существующим реестром RMI.
Метод registerClient (строки 72-82) дает возможность удаленным объектам
ChatClient быть зарегистрированными сервером ChatServer для участия в сеансе
интерактивного общения. Параметр ChatClient, передаваемый методу register-
Client, представляет собой удаленную ссылку на зарегистрированного клиента,
который сам представляет собой удаленный объект. В строке 77 удаленная ссылка
ChatClient добавляется в множество (Set) объектов ChatClient, участвующих в
сеансе интерактивного общения. Метод unregisterClient (строки 85-95) дает
возможность клиентам ChatClient выходить из сеанса. В строке 90 заданная
удаленная ссылка ChatClient удаляется из множества ссылок ChatClient.
Клиенты ChatClient вызывают удаленный метод postMessage (строки 98-124)
для размещения новых сообщений ChatMessage в сеансе интерактивного общения.
Каждый экземпляр объекта ChatMessage (рис. 2.18) представляет собой сери ал и-
зуемый объект, который содержит в качества свойств отправителя сообщения
и тело сообщения. В строках 109-123 осуществляется обработка множества
ссылок ChatClient и вызывается удаленный метод dellverMessage интерфейса
ChatClient для доставки нового сообщения ChatMessage каждому из клиентов. Если
при доставке сообщения клиенту выдается исключение, предполагается, что
клиент больше не доступен. В связи с этим в строке 121 отменяется регистрация на
сервере отсутствующего клиента.
Интерфейс Stoppable ChatServer требует, чтобы ChatServerlmpl ре али зовы вал
метод stopServer (строки 128-170). В строках 140-143 обрабатывается множество
ссылок ChatClient и вызывается метод server Stopping интерфейса ChatClient для
уведомления каждого клиента ChatClient, что сервер отключается. В строках
147-168 создается и запускается новый программный поток Thread, чтобы класс
Chat Server Administrator (рис. 2.15) мог разорвать связь удаленного объекта с
реестром RMI до завершения выполнения удаленного объекта.
Класс Chat Server Administrator (рис. 3.15) представляет собой утилиту для
регистрации и отмены регистрации активируемого удаленного объекта ChatServer.
Метод startServer (строки 14-52) запускает активируемый объект ChatServer.
Активируемые объекты RMI выполняются как часть класса ActivationGroup
(пакет java.nnl.acti vat ion). Демон активации RMI запускает новую виртуальную
машину для каждого объекта ActlvationGreop. В строках 21-22 создается объект
Properties и добавляется свойство, задающее файл политики, в соответствии с
правилами которого будет выполняться виртуальная метина для объекта
ActivationGroup. Этот файл политики (рис. 2.16) дает возможность активируемым
объектам в группе ActivationGroup завершать работу виртуальной машины для
данной группы активации. Напомним, что класс ChatServerlmpl вызывает
статический метод exit класса System в методе stopServer, который завершает работу
виртуальной машины для грунны ActivationGroup вместе со всеми ее
выполняющимися удаленными объектами.
Удаленный вызов методов
1 // ChatServerAdministrator.Java
2 // ChatServerAdministrator - программа-утилита для валуем
3 // и останова активируемого объекта ChatServer.
4 package com.deitel.messenger.rmi.server;
5
6 // Набор базовых пакетов Java
7 import java.rmi.*;
8 import java.rmi.activation.*;
9 import java.util.*;
10
11 public class ChatServerAdministrator {
12
13 // настройка активируемого серверного объекта
14 private static void stsrtServer{ String policy,
15 String codebase ) throw» Exception
16 {
17 // установка менеджера безопасное** RMX
18 System.setSecurityHanager( new RMZSecurityManager{) );
19
20 // задание политики безопасности для ActivataoleGroup JVM
21 Properties properties = new Properties();
22 properties.put( "Java.security.policy", policy );
23 // создание дескриптора ActivationGroupDesc
24 // для активируемого объекта
25 ActivationGroupDesc groupDesc =
26 new ActivationGroupDesc{ properties, null );
27
28 // регистрации группы ентмжацкм системой активации RMZ
29 ActivationGroupXD groupZD »
30 ActivationGroup.getSystem().ragisterGroup( groupDesc );
31
32 // создание группы активации
33 ActivationGroup.createGroup( groupID, groupDesc , 0 ) ,-
34
35 // описание активации для ChatServarlmpl
36 ActivationDesc description = new ActivationDesc(
37 "com.deitel.messenger.rmi.server.ChatServarlmpl",
38 codebase, null );
39
40 // регистрация описания с помощью rmid
41 ChatServer server =
42 ( ChatServer ) Activatable.register( description );
43 SysteH.out.printing "Obtained ChatServerImpl stub" ) ;
44
45 // привязка ChatServer к peecvpy
46 Naming.rebind( "ChatServer", server );
47 System.out.printing "Bound object to registry" );
48
49 // завершение программы установки
50 SyateH.exit{ 0 );
51
52 ) // конец метода startSarver
53
54 // останов сервера
55 private static void terminateServer( String hostname )
56 throws Exception
58 // поиск сервера ChatServer в реестре RMI
59 System.out.println( "Locating server ..." );
60 StoppableChatServer server = ( StoppableChatServer )
61 Naming.lookup{ "rmi;//'' + hostname + "/ChatServer" J ;
62
63 // останов сервера
64 System.out.println( "Stopping server ..." );
65 server.stopServ«r();
66
67 // удаление сервера ChatServer ив реестра RMI
68 System.out.printing "Server stopped" );
69 Naming.unbind( "rml;//" + hostname + '"/ChatServer" ) ;
70
"71 } // конец метода terminate Server
72
73 // запуск прилсиения ChatServerAdministrator
74 public static void main( String args[] ) throve Exception
75 i
76 // проверка значения параметра для останова сервера
77 if ( args.length -= 2 ) {
78
79 if ( args[ 0 ).equals( "stop" ) )
80 terminateServer{ args[ 1 ] );
81
82 else printUsagelnstruetions();
83 }
84
85 // проверка значения параметра для запуска сервера
86 else if ( args.length =3)1
87
88 // запуск сервера с предоставленными польвоватенеи
89 // политикой, базой кодов и адресом реестра
90 if ( args[ 0 ].equals* "start" ) )
91 staxtServer( args [ 1 ], argef 2 ] ),-
92
93 else printosagelnstructionsf);
94 )
95
96 // неверное число параметров - вывод инструкций
97 else printOsagelnstruetions{);
98
99 } // конец метода main
100
101 // вывод инструкций для запуска приложения ChatServerAdministrator
102 private static void printusageInstructions{)
103 {
104 System.err.printing "\nUsage:\n" +
105 "\tjava com.deitel.messenger.rmi.server," +
106 "ChatServerAdministrator start <policy> <codebase>\n" +
107 "\tjava com.deitel.messenger.rmi.server." +
108 "ChatServerAdministrator stop <registry hostname>" ) ,-
109 }
110 } ^_
Рис. 2.15. Приложение ChatServerAdministrator для запуска и останова удаленного
объекта ChatServer
Удаленный вызов методов 57
1 // разрешение группе активации ActivationGroup завершать работу
виртуальной машины
2 grant {
3 permission java.lang.RuntlnePermleeion "«xifcVM";
4 );
Рис. 2.16. Файл политики для ActivationGroup сервера Chat Server
В строках 25-26 создается объект ActivationGroupDesc, который является
дескриптором группы активации. Дескриптор группы активации задает
информацию о конфигурации для объекта ActivationGroup. Первым параметром
конструктора ActivationGroupDesc является ссылка на объект Properties, который
содержит значения системных свойств виртуальной машины для ActivationGroup.
В этом примере мы замещаем системное свойство java.security.policy, чтобы
предоставить соответствующую политику безопасности для виртуальной машины
группы ActivationGroup. Вторым параметром является ссылка на объект Active -
tionGroupDescCommandEnvironment. Это дает возможность объекту
ActivationGroup настраивать команды, которые демон активации выполняет при запуске
виртуальной машины для ActivationGroup. В данном примере такой настройки не
требуется, поэтому в качестве второго параметра мы передаем null.
В строках 29-30 осуществляется получение объекта Activation System,
вызывав статический метод get System класса ActivationGroup. В строке 30 вызывается
метод registerGroup интерфейса Activation System и ему передается в качестве
параметра дескриптор группы активации groupDesc. Метод registerGroup
возвращает объект ActivationGroupID для вновь зарегистрированного объекта
ActivationGroup. В строке 33 вызывается статический метод createGreup класса
ActivationGroup для создания объекта ActivationGroup. Этот метод принимает в качестве
параметров объект ActivationGroupID, объект ActivationGroupDesc и номер
воплощения для группы ActivationGroup. Номер воплощения идентифицирует
различные экземпляры одного и того же объекта ActivationGreup. Каждый раз,
когда демон активации активизирует группу ActivationGroup, он увеличивает номер
воплощения.
В строках 36-38 создается объект ActivatlonDesc для удаленного объекта
Chat Server. Дескриптор активации задает информацию о конфигурации для
конкретного удаленного объекта Activatable. Первый параметр конструктора
ActivatlonDesc задает имя класса, который реализует удаленный объект Activatable.
Второй параметр задает базу кодов, которая содержит файлы классов удаленного
объекта. Последним параметром является ссылка на Marsha 11 edObject — объект,
который задает инициализирующую информацию для удаленного объекта.
Вспомним, что конструктор активации ChatServerlmpl принимает в качестве своего
второго параметра ссылку на Marsha lledObject. Наш удаленный объект Chat Server
не требует специальной инициализирующей информации, поэтому в строке 33
передается null в качестве второго параметра MarshalledObject.
В строке 42 вызывается статический метод register класса Activatable, чтобы
зарегистрировать удаленный объект Activatable. Метод register принимает в качестве
параметра дескриптор ActivationDesc для объекта Activatable и возвращает ссылку
на заглушку удаленного объекта. В строке 46 вызывается статический метод rebind
класса Naming для связывания объекта Chat Server с реестром Registry RMI.
Метод terminateServer (строки 55-71) предоставляет средства для отключения
активируемого удаленного объекта Chat Server. В строке 61 вызывается
статический метод lookup класса Naming для получения удаленной ссылки на объект
Chat Server. В строке 60 осуществляется приведение ссылки к типу Stopped-
Chat Server. В строке 65 вызывается метод stopServer для уведомления клиентов,
что сервер ChatServer отключается. Напомним, что метод stopServer класса
Глава 2
Cha t Server Imp 1 запускает поток Thread, который ожидает нить секунд, прежде
чем вызвать статический метод exit класса System. Этот поток сохраняет
удаленный объект Chat Server выполняемым после возврата из метода stop Server, давая
возможность объекту ChatServerAdministrator удалить удаленный объект из
реестра RMI. В строке 69 вызывается статический метод unbind класса Naming для
удаления удаленного объекта ChatServer из реестра RMI. После этого поток
Thread в классе ChatServerlmpl завершает работу виртуальной машины, в
которой выполнялся удаленный объект ChatServer.
Метод main (строки 74-99) проверяет параметры командной строки, чтобы
определить, следует ли запустить или остановить удаленный объект ChatServer. При
останове сервера пользователь должен предоставить в качестве второго параметра имя
компьютера, на котором выполняется сервер. При запуске сервера пользователь
должен предоставить в качестве параметров местоположение файла политики для
группы ActivationGronp и базы кодов для удаленного объекта. Если пользователь
передает в качестве параметр «stop», в строке 80 вызывается метод terminate-
Server, чтобы отключить сервер ChatServer на указанном компьютере. Если
пользователь передает параметр «start», в строке 91 вызывается метод startServer с
заданным местоположением файла политики и базы кодов. Если пользователь
предоставляет неверное количество параметров или неправильный тип параметров, в
строках 82, 93 и 97 вызывается метод prmtUsagelnstructions (строки 102-109) для
отображения информации об обявательных параметрах командной строки.
2.6.2. Архитектура и реализация клиента в Deitel Messenger
На протяжении книги мы представляем несколько версий учебного
приложения Deitel Messenger. Клиент для приложения Deitel Messenger использует
модульную архитектуру, чтобы можно было повторно использовать код в нескольких
версиях практического примера.
Коммуникационные интерфейсы и их реализация
Клиент для приложения Deitel Messenger разделяет пользовательский
интерфейс приложения и средства сетевого взаимодействия, помещая их в отдельные
объекты, которые взаимодействуют через набор интерфейсов. Это дает нам
возможность использовать один и тот же клиентский пользовательский интерфейс
для различных версий приложения Deitel Messenger. В этом разделе мы
представляем эти интерфейсы и их реализации с помощью RMI.
Интерфейс ChatCllent (рис. 2,17) представляет собой удаленный интерфейс
RMI, который дает возможность серверу ChatServer взаимодействовать с
клиентом ChatClient через обратные вызовы RMI — удаленные вызовы методов
сервером ChatServer на клиенте. Напомним, что когда клиент соединяется с сервером
ChatServer, клиент вызывает метод registerClient интерфейса ChatServer и
передает в качестве параметра удаленную ссылку на ChatClient. Сервер затем
использует эту удаленную ссылку для обратных вызовов RMI на клиенте ChatClient
(например, для доставки сообщений Chat Message этому клиенту). Метод deliver-
Message (строки 16-17) дает возможность серверу ChatServer посылать новые
сообщения ChatMessage клиенту ChatCllent. Метод server Stopping (строка 20)
дает возможность серверу ChatServer уведомлять клиента ChatClient об
отключении сервера ChatServer.
1 // ChatCllent.Java
2 // ChatClient -удаленный интерфейс, который определяет нетолы
3 // для получения клиентом сообщений и информации о состоянии от
4 // сервера ChatServer.
5 package com.deitel.messenger.nni.client;
Удаленный вызов методов
7 // Набор базовых пакетов Java
8 import java.rmi.*;
9
10 // Пакеты Deitel
11 import com.deitel.messenger.rmi.CnatMessage;
12
13 public interface ChatClient extends Remote {
14
15 // метод, вызываемый серверов для доставки сообщения клиенту
16 public void deliveritessage( CnatMessage mesaage )
17 throws RemoteException;
18
19 // метод, отзываемый при останове сервера
20 public void serverStopping{) throws RemoteBxception;
21 1 __^__^_____^_
Рис. 2.17. Удаленный интерфейс ChatClient, разрешающий обратные вызовы RMI
Класс CnatMessage (рис. 2.18) представляет собой сериалиэуемый класс
сообщений в системе Deitel Messenger. Переменные экземпляров sender и message
содержат имя лица, отправившего сообщение, и тело сообщения, соответственно.
Класс CnatMessage предоставляет методы set и get для переменных sender и mee-
sege и метод to String для формирования строкового представления сообщения
CnatMessage.
1 // ChatMessage.Java
2 // CnatMessage - сериализуемнй объект для
3 // сообщений клиента ChatClient и сервера ChatServer в RMZ.
4 package com. deitel. messenger .mi;
5
6 // Набор валовых пакетов Java
7 import java.io.*;
e
9 public class CnatMessage implements Serializable {
10
11 private String sender; // лицо, отправляютв сообщение
12 private String message; // отправляемое сообщение
13
14 // создание пустого сообщения CnatMessage
15 public CnatMessage0
this{ "", "" );
// соэдаыве сообщения CnatMessage с указанием отправителя
//и текста сообщения
public CnatMessage{ String sender. String message )
setSender( sender );
setMessage( message );
// аадамме имени липа, отправлявшего сообщение
public void setSender{ String name )
60 Глава 2
30 sender = name;
31 )
32
33 // получение ниени лица, оиправляжщего сообщение
34 public String getSender()
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56 1
Рис. 2.18. Класс ChatMessage - сериализуемый класс для передачи сообщений через RM1
Интерфейс MessageManager (рис. 2.19) объявляет методы для классов, которые
реализуют логику коммуникационного взаимодействия для клиента Chat Client.
Методы, которые объявляет этот интерфейс, не являются специфичными для
реализаций базового коммуникационного взаимодействия. Пользовательский
интерфейс клиента системы интерактивного общения использует реализацию Message-
Manager для установки и разрыва соединения с сервером Chat Server и отправки
сообщений. Метод connect (строки 10-11) устанавливает соединение с сервером
Chat Serve г и принимает в качестве параметра слушатель Message Listener,
которому MessageManager будет доставлять новые входящие сообщения. Мы подробно
обсудим интерфейс Message Listener, когда будем рассматривать
пользовательский интерфейс клиента. Метод disconnect (строки 15-16) разрывает соединение
(отключает объект MessageManager от сервера Chat Server) и прекращает
пересылку сообщений заданному слушателю Message Listener. Метод sendMessage
(строки 19-20) принимает в качестве строковых параметров имя пользователя
(from) и сообщение message, отправляемое на сервер Chat Server. Метод setDisco 11-
nect Listener регистрирует, слушателя Disconnect Listener, который будет
уведомляться при разрыве соединения сервера ChatServer с клиентом. Интерфейс Dis-
connectListener мы подробно обсудим при рассмотрении пользовательского
интерфейса клиента.
1 // MessageManager.java
2 // MessageManager - интерфейс для объектов, способных управлять
3 // взаимодействием с серверов сообщений.
4 package com.deitel.messenger;
return sender;
// задание отаравляемот'о сообщения
public void setMessagef String messageBody )
// получение отправляемого сообщения
public String getMessage()
return message;
// строковое представление сообщения ChatMes
public String toStringO
return getSender{) + "> " + gefcMessage();
Удаленный вызов методов
6 public interface MessageManager {
7
8 // соединение с сервером сообщений и направление
9 // входящих сообщений заданному слушателю MessageListener
10 public void connect{ HessageListener listener )
11 throw* Exception;
12
13 // отключение от сервера сообщений и прекращение передачи
14 // входящих сообщений заданному слушателю HessageListener
15 public void disconnect( HeasageListener listener )
16 throws Exception;
17
18 // отправка сообщения на сервер сообщений
19 public void sendMessage{ String from, String message )
20 throws Exception;
21
22 // задание слушателя для уведомления об отключении
23 public void setDieconnectListener(
tctListener listener );
Рис. 2.19. Интерфейс MessageManager для классов, которые реализуют логику
коммуникационных взаимодействий для клиента ChatClient
Класс RMIMessageManager (рис. 2.20) обслуживает все коммуникационные
взаимодействия между клиентом и сервером ChatServer. Класс RMIMeesageMana-
ger представляет собой уданенныи объект RMI, который расширяет класс Unicast-
RemoteObject и реализует удаленный интерфейс ChatCUent (строки 18-19). Класс
RMIMessageMenager также реализует интерфейс MessageManager, который дает
возможность пользовательскому интерфейсу использовать объект RMIMessageMa-
nager для взаимодействия с сервером ChatServer.
1 // RMLHeeeageManager.Java
2 // RMIMaeaageManageE реализует удаленный интерфейс ChatClient
3 // и управляет входящими и исходящими сообщениями с помощью KMI.
4 package com.deitel.messenger.rmi.client;
5
6 // Набор базовых пакетов Java
7 import java.awt.*;
8 import Java.awt.event.*;
9 import java.xmi.*;
10 import Java.rmi.server.*;
11 import java.util.*;
12
13 // Пакеты Deitel
14 import com.deitel.messenger.*;
15 import com.deitel.messenger.rmi.*;
16 import com.deitel.messenger.rmi.server.ChatServer;
17
19 public class RMlMessageHanager extends OnicastRemoteObject
19 implements ChatClient, MessageManager {
20
21 // слушатели для входящие сообщений и уведомлений об отключения
22 private HessageListener messageListener;
23 private DisconnectListener disconnectListener,-
private String serverJhddress;
private ChatServer chatServer;
// xoROspyxvop йиПИвввадвИапддег
public РМХКеамдеНападег { String server ) throws RemoteException
{
serverAddress » server;
1
// соадинаюм с сервером ChatServer
public void connect! MessageListener listener )
throws Exception
{
// поиск удалеяшого объекта ChatServer
chatServer = ( ChatServer ) Naming.lookup(
"rml://" + serverAddress + "/ChatServer" );
// регистрация сервером ChatServer для приема сообщения
chatServer. register-Client ( this ) ;
// вадакке слушателя для входящих сообщений
mesaageLiatener = listener;
) // конец ывтода connect
// отключение о* сервера ChatServer
public void disconnect{ HesBagaListener listener )
throws Exception
{
if ( chatServer ■= null )
return;
// отмеыв регистрации сервером ChatServer
chatServer.unregisterClient( this );
// удаяеыне ссылок ш сереер ChatServer и слушателя
// MessageListener
chatServer ■= null;
messageListener = null;
) // конец метода disconnect
// омпраика сообщения ChatHessage на сервер ChatServer
public void aendttesaagef String fromHser, String message )
throws Exception
{
if { chatServer = null )
// создание сообщения ChatHessage с текстом и иыенеи пользователя
Chatrleasage ChatHessage =
new ChatMe**age( fromOaer, message );
// размещение сообщения ш сервере ChatServer
chatServer.postllessage{ ChatHessage ) ,-
Удаленный вызов методов
// конец ынтода sendMessage
// обработка доставки сообщения Cha message o« сервера ChatServer
public void delivarMessage( ChatMassage message )
throws RemoteExcaption
if { messagsListener != null )
mesaageListener.messageReceived{ message.getSander{),
message.getMessage() );
// метод, вызываемый при оставове сервера
public void serverstopping() throws RemoteExcaption
100
101
102
103
104
105
106
107
108
109
110
111 1
ChatServer = null;
fireServerDisconnected( "Server shut down.'
// регистрация слушателей для уведомлений об
public void setDisconnectliiяtener {
Disconnectliistener listener )
disconnectListener = listener;
// отправка уведомления об отключении
private void fireServerDisconnected{ String message }
if ( disconnectListener ! = null )
dieconnectListenar.serverDieconnected{ message );
Рис. 2.20. Удаленный объект RMIMessageManager и реализация MessageManager для
управления взаимодействием с клиентом ChatClTent
Конструктор RMIMessageManager принимает в качестве строкового параметра
имя компьютера, на котором выполняется реестр RMI и где был зарегистрирован
объект ChatServer. Заметим, что поскольку класс RMIMessenger сам является
удаленным объектом RMI, конструктор RMIMessageManager возбуждает
исключение RemoteException, которое является обязательным для всех подклассов
UnicastRemoteObject. В строке 31 указанное имя сервера присваивается
переменной экземпляра server Address.
Метод connect (строки 35-43), объявленный в интерфейсе MessageManager,
осуществляет соединение RMIMessageManager с сервером ChatServer. В строках
39-40 вызывается статический метод lookup класса Naming для извлечения
удаленной ссылки на объект ChatServer. В строке 43 вызывается метод registerCHent
интерфейса ChatServer для регистрации RMIMessageManager для обратных
вызови в RMI от ChatServer. Обратите внимание, что в строке 43 методу registerCtient
передается ссылка this в качестве параметра. Напомним, что класс
RMIMessageManager является уделенным объектом, следовательно, ссылка this может
служить в качестве удаленной ссылки ChatCHent на удаленный объект
RMIMessageManager.
Метод disconnect (строки 51-64) отключает объект RMIMessageManager от
сервера ChatServer. Если уделенная ссылка ChatServer класса ChatServer равна
64
Глава 2
nail, в строке 55 возврат осуществляется немедленно, поскольку объект RMIMes-
sageManager уже отсоединен. В строке 58 вызывается метод unregisterClient
удаленного интерфейса ChatServer, чтобы отменить регистрацию объекта RMIMessa-
ge Manager на сервере Chat Server. В строке 58 ссылка this передается методу
unregisterClient в качестве параметра, задавая, что сервер Chat Server должен
отменить регистрацию этого уделенного объекта RMIMessageManager. В строке 62
задается значение null для ссылки messageListener класса MessageListener. .
Метод sendMessage (строки 67-80) доставляет сообщение от клиента серверу
ChatServer. В строке 71 осуществляется немедленный возврат, если удаленная
ссылка chat Server равна null. В строках 74-75 создается новый объект Chat-
Message, содержащий имя пользователя, от которого поступило сообщение, и тело
сообщения. В строке 78 вызывается метод postMessage удаленного интерфейса
ChatServer для размещения нового сообщения ChatMessage на сервере Chat-
Server. Сервер ChatServer будет использовать обратные вызовы RMI для доставки
этого сообщения каждому зарегистрированному клиенту ChatClient.
Метод deliver Message (строки 83-89), определенный в удаленном интерфейсе
ChatClient, дает возможность объекту ChatServer использовать обратные вызовы
RMI для доставки входящих сообщений ChatMessage клиенту ChatClient. Бели
имеется слушатель MessageListener, зарегистрированный классом
RMIMessageManager (строка 86), в строках 87-88 вызывается метод meesageReceived
интерфейса MessageListener, чтобы уведомить слушателя MessageListener о входящем
сообщении ChatMessage. В строках 87-88 вызываются методы getSender и get-
Message класса ChatMessage для извлечения отправителя сообщения и тела
сообщения, соответственно.
Метод serverStopping (строки 92-96), определенный в уделенном интерфейсе
ChatClient, дает возможность серверу ChatServer использовать обратные вызовы
RMI, чтобы уведомить клиента ChatClient, что сервер ChatServer
останавливается, чтобы клиент ChatClient мог разорвать соединение и уведомить слушателя
DisconnectListener. В строке 95 вызывается метод fireServerDisconnected класса
RMIMessageManager, чтобы уведомить зарегистрированного слушателя
DisconnectListener, что оервер ChatServer отключил клиента ChatClient.
Метод setDiseonnectListener (строки 99-108), определенный в интерфейсе
MessageManager, дает возможность слушателю DisconnectListener
регистрировать уведомления при отключении клиента сервером ChatServer. Например,
пользовательский интерфейс клиента может регистрировать эти уведомления, чтобы
известить пользователя об отключении сервера. Метод fireServerDisconnected
(строки 106-110) представляет собой метод для отправки сообщений server-
Disconnected слушателю DisconnectListener. Если имеется зарегистрированный
слушатель DisconnectListener, в строке 109 вызывается метод server Disconnected
интерфейса DisconnectListener для уведомления слушателя, что сервер
отсоединен. Мы подробно обсудим интерфейс DisconnectListener, когда будем
рассматривать пользовательский интерфейс клиента.
Клиентский интерфейс пользователя и его реализация
Мы отделили пользовательский интерфейс клиента от реализации
MessageManager посредством интерфейсов MessageListener и DisconnectListener (рис. 2.19
и 2.20). Класс ClientGUI использует реализации интерфейсов MessageListener
и DisconnectListener для взаимодействия с классом MessageManager и
предоставляет графический пользовательский интерфейс клиенту.
Интерфейс MessageListener (рис. 2.21) дает всеможность объектам класса
реализации принимать входящие сообщения от MessageManager. В строке 9
определен метод meesageReceived, который принимает в качестве параметра имя
пользователя, от которого поступило сообщение, и тело сообщения.
Удаленный вызов методов 65
1 // HessageListener. Java
2 // HessageListener - интерфейс для классов, которна хотят
Ъ II получать новые сообщения.
4 package com.deitel.messenger;
5
Б public interface HessageListener {
7
5 // получения нового сообщения
9 public void messageReceived{ String from. String message );
10 )
Рис. 2.21. Интерфейс MessageLirtener для приема новых сообщений
Интерфейс DisconnectListener (рис. 2.22) позволяет объектам реализации
принимать уведомления, когда сервер отсоединяет объект Message Manager. В строке
9 определен метод serverDisconnected, который принимает в качестве строкового
параметра сообщение message, которое указывает, по какой причине сервер
разорвал соединение.
1 // DisconnectListener.Java
2 // DisoonnectListener определяет метод serverDisconnected,
3 // который указывает, что сервер отключил клиента.
4 package com.deitel.messenger;
5
6 public interface DisconnectListener (
7
8 // получение уведомления, «то сервер отключен
9 public void serverDisoonnected( String message );
io i :
Рис. 2.22. Интерфейс DisconnectListener для приема уведомлений об отсоединении
сервером
Класс ClieatGUl (рис. 2.23) предоставляет пользовательский интерфейс для
клиента Deitel Messenger. Пользовательский интерфейс состоит из меню и панели
инструментов с действиями Action для соединения с сервером и отсоединения от
сервера ChatServer, текстовой области JTextArea для отображения входящих
сообщений Chat Message, текстовой области JTextArea и кнопки JBatton для
отправки новых сообщений на сервер ChatServer. В строках 27-29 объявляются
ссылки на действия Action для соединения с сервером и отсоединения от сервера
ChatServer, а также отправки сообщений ChatMessage. В строке 35 объявляется
ссылка типа MessageManager для реализации MessageManager, которая
обеспечивает сетевое взаимодействие. В строке 88 объявляется ссылка типа Message-
Listener для приема новых сообщений от сервера ChatServer через интерфейс
Mess age Manager.
1 // ClientGUI.iava
2 // ClientGUI предоставляв* пользовательский интерфейс для отправки
3 // и получения сообщений с помощью MessageManager.
4 package com.deitel.messenger;
5
6 // Набор бавовюс пакетов Java
7 import java.awt.*;
8 import Java.awt.event.*;
9 import java.util.*;
3 3u 204
Глава 2
10
11 // Пакеты расширений Java
12 import java*.swing.*;
13 Import javax.swing.border.*;
14 import javax.swing,text.*;
15
16 public class ClientGDI extends JFrame {
17
18 // надпись JLabel для отображения состояния соединения
19 private JLabel statusBar;
20
21 // текстовые области JTextArea для отобрашения и ввода сообщений
22 private JTextArea messageArea;
23 private JTextArea inputArea;
24
25 // действия для подключения и отключения менеджера
26 // HessageHanager и отправки сообщений
27 private Action connectAction;
28 private Action disconnectAction;
29 private Action sendAction;
30
31 // кия пользователя userName, добавляемого х исходящим сообщениям
32 private String userName = "";
33
34 // ывнеджер HessageHanager для взаимодействия с сервером
35 HessageHanager messageHanager;
36
37 // слушатель HeasageLiatener при приема новых сообщений
38 HessageListener mesaageListener;
39
40 // конструктор ClientGUI
41 public ClientGDI{ HessageHanager manager )
42 {
43 super( "Deitel Messenger" );
44
45 meesageHanager = manager;
46
47 messageListener = new HyHessageListenerO ;
48
49 // соадамне действий
50 connectAction = new ConnectAction{);
51 dieconnectAction = new DisconnectAction{);
52 disconnectAction.aetEnabled{ false );
53 sendAction = new SendAction{);
54 sendAction.setEnabled( false );
55
56 // настройка мен» File
57 JHenu filaManu = new JMenu ( "File" );
58 fileHenu.setMnemonic( 'F' );
59 fileHenu.add( connectAction );
60 fileHenu.add( disconnectAction );
61
62 // настройка павеля менк> JMenuBar и прмсовдикехие меню File
£3 JMenuBar menuBar = new JMenuBar{);
64 menuBar.add { fileHenu );
65 setJMenuBar{ menuBar ) ;
Удаленный вызов методов
67 // настройка панели инструментов JToolBar
68 JToolBar tooLBar = new JToolBarО;
69 toolBar.add{ connectAction );
70 toolBar.add( disconnectAction );
71 // создание текстовой области JTaxtArea
72 // для отображения сообщений
73 messageArea => new JTextArea{ 15, 15 );
74
75 // запрет редактирования и переноса слов а конце строжи
76 messageArea.setEditable{ false );
77 messageArea.satLineWrap{ true );
78 messageArea.setwrapstyleWord( true );
79
80 JPanel panel = new JPanel();
81 panel.setbayout{ new BorderLayout( 5, 5 ) );
82 panel.add( new JScrollFane( messageArea ),
83 BorderLayout.CENTER );
84 // создание текстовой области JTextArea
85 // для ввода новых сообщения
66 inputAxea = new JTextArea{ 3, 15 );
87 inputAxea.aetLineWrap( true );
88 inputAxea.setflrapStylenord{ true );
89 inputArea.setEditable( false );
90 // связывание клавиши Enter ■ компоненте inputAxea
91 // с действием sendAction
92 Keymap JceyMap = inputAxea. getKeymap () ;
93 Keystroke enterKey = Keystroke.getKeyStroke{
94 KeyEvent.VK_ENTER, 0 );
95 keyMap.addActionEorKeyStxoke{ enterKey, sendAction ) .-
96
97 // размещение кнопок inputAxea и sendAction а панели BoxLayout
98 // и добавление компонеета Box в панель гаевsagePanel
99 Box box = new Box( BoxLayout. X__AXIS );
100 box.add( new JScrollPanef inputArea ) );
101 box.add( new JButton{ sendAction ) );
102
103 panel.add( box, BorderLayout.SOUTH );
104
105 // создание надписи statusBar с ранкой '
106 statusBar = паи JLabelf "Not Connected" );
107 statusBar.aetBorder(
108 new BavelBorder( BavelBorder.LOWERED ) );
109
110 // размещение компонентов
111 Container container = getContentPa/ie{) ,-
112 container.add{ toolBar, BorderLayout.HORTH );
113 container.add( panel, BorderLayout.CESTER );
114 container.add{ statusBar, BorderLayout.SOOTH );
115
116
117
118
119 new HindowAdapterO (
120
121 // откдпчеыве MesaageManagar при закрытии окла
122 public void windowClosing{ WindowEvent event )
123 {
124 // отключение от сервера
125 try {
12G messageHanager.disconnect{ messageListener );
127 1
128
129 // обработка исключения при отключения от сервера
130 catch { Exception exception ) (
131 exception.printStackTrace{);
132 )
133
134 System.«xit( 0 );
135
136 ) // конец ывтода windowClosing
137
138 ) // конец внутреннего класса WindowAdapter
139 ) ;
140
141 } I/ конец конструктора ClientGDI
142
143 // действия для соединения с серверов
144 private class ConnectAction extends AbstractAction {
145
146 // ывстройка действия ConnectAction
147 public ConnectAction()
148 {
149 putValue( Action.NAME, "Connect" );
150 putValue( Action.SHALL_ICON, new ImagaIcon{
151 ClientGUl.class.getRasource{
152 "images/Connect.gif" ) ) );
153 putvalue{ Action.SHORT_DESCRIPTION,
154 "Connect to Server" );
155 putValue( Action. LOWG_DESCRIPTIC*l,
156 "Connect to server to send Instant Messages" );
157 putValue{ Action.KNEMOHIC_KEY, new Integer( 'C ) ),
158 )
159
160 // соединение с сервером
161 * public void actionPerformed{ ActionEvent event )
162 {
163 // соединение MessageHanager с сервврок
164 try (
165
166 // очистка области messageArea
167 messageArea.setText( "" );
168 // подклвяехие HessageHanager и регистрация слушателя
169 // MessageListener
170 messageHanager.connect( messageListener );
171
172 // прослушивание уведомлений об отключении
173 messageHanager.setDisconnectListener{
174 new DisconnectHandler() );
175
176 // получение имени пользователя userName
177 uaerName = JOptionPane.showlnputDi&log{
Удаленный вызов методов i
178 ClientGOI.this, "Please enter your name: " ) ;
179 // обновление действий Action, области ввода inputAre:
180 // и строки состояния statusBar
181 connectAction.setEnabledf false );
182 disconnectAction.setSnabled{ true );
183 sendAction.setEnabled{ true );
184 inputArea.setEditable( true );
1S5 InputArea.requestFocusO;
186 etatusBar.setText{ "Connected: " + userName );
181 // отправка сообщения, укаэывающех>о ка подключение
188 // пользователя
189 roessageManager.sendHesaagef userName, userName +
190 " joined chat" );
191
192 ) // котец оператора try
193
194 // обработка исключения при соединении с сервером
195 catch ( Exception exception ) {
196 JOptionPane.showHessageDialog( ClientGOI.this,
197 "Dnable to connect to server.", "Error Connecting",
198 JOptionPane.ERROR_MESSAGE );
199
200 exception.printStackTraceO ,-
201 )
202
203 } II конец метода actionPerfonned
204
205 ) // конец внутреннего класса ConnectAction
206
207 // действия дин отключения от сервера
208 private class DisconnectAction extends AbstractAction {
209
210 // настройка действии DisconnectAction
211 public DisconnectAction{)
212 (
213 putValue{ Action.NAME, "Disconnect" );
214 putValue( Action.SMALL_ICON, new ImageIcon(
215 ClientGOI.class.getBesource(
216 "images/Disconnect.gif" ) ) ) ,-
217 putValuet Action.SHORT_DESCRIPTION,
218 "Disconnect from Server" );
219 putValuet Action.LONG_DESCRIPTIOH,
220 "Disconnect to end Instant Messaging session" );
221 putValue( Action.MHEMONICJCEY, new Integer( 'D' ) );
222 )
223
224
225
226
227 // отключение менеджера MessageManager <
228 try (
229 // отправка сообщения, укаэивающего,
230 // что пользователь отключен
231 messageManager.sendMeBsaget userName, userName -
232 " exited chat" );
234 // отключение от сервера и отмена регистрации
235 // слушателя MessageListener
236 roessageHanagex.disconnect! roeasageListener );
237 // обновление действий Action, области ввода inputArea
238 // и строки состояния statusBax
23» sendAction.setEnaoled( false );
240 disconnectActi.on.setEnaoled( false );
241 inputArea.setEditable( false );
242 connectActi.on.setEnabled{ true ) ;
243 statuaB&r.setText( "Not Connected" );
244
245 ) // конец оператора try
246
247 // обработка исключения при отключении от сервера
248 catch ( Exception exception ) (
249 JOptionPane.showMessageDialogf ClientGUl.this,
250 "Doable to disconnect from server.",
251 "Error Disconnecting", JOptionPane,ERROR_MESSAGE );
252
253 exception.printstacxTrece();
254 )
255
256 ) // конец метода actionРегformed
257
25В ) // конец внутренних классов DisconnectAction
259
260 // действие для отправки сообщений
261 private class SendAction extends AbatcactAction (
262
263 // настройка действия SendAction
264 public SendActionO
265 {
266 putvalue( Action.NAME, "Send" ) ;
267 putValue( Action.SMALL_ICOH, new ImageIcoc(
268 ClientGUl.class.getKesource( "images/Send.gif" ) ) );
269 putValuet Action.SHORT_DESCRIPTION, "Send Message" );
270 putValuet Action.LONG_DESCRIPTIONr
271 "Send an Instant Message" );
272 putValuet Action.HNEMOSIC_KEY, new Integer( 'S1 ) );
273 )
274
275 // отправка сообщения и очистка области inputArea
276 public void actionPerformed{ ActionEvent event )
277 (
278 // отправка сообщения на сервер
279 try (
280 // отправка ннени пользователя userName и текста
281 // из области inputArea
282 messageHanager.sendMessage( userName,
283 inputArea.getText() );
284
285 inputArea.setText( "" );
286 }
287
288 // обработка исключении при отправке сообщения
Удаленный вызов методов . 71
289 catch { Exception exception ) (
290 JOptionPane.showMessageDialog( ClientGUI.this,
291 "Unable to send message.", "Error Sending Message",
292 JOptionPane.ERROR_MESSAGE );
293
294 exception.printStackTrace();
295 )
296
297 ) // конец метода actionPerformed
298
299 ) // конец внутреннего класса SendAction
300
301 // HyMessageListanex прослушивает новые сообщения от
302 // MeseageManageir и отображает сообщения в области
303 // messageArea с помощью HassageDisplayer.
304 private class HyMessageListaner implements MessageLiataner (
305 // при получении пояьаователам нового сообщения отобрааить
306 // его в messageArea
307 public void messageReceived( String from, String massage )
308 {
309 // добавление сообщения с помощью Messagedsplay*r и
310 // invokeLater дин обеспечения безопасного доступа
// к messageArea
311 SwingOtiliti.es. involceLater (
312 new HessageDisplayer( from, massage ) );
313 >
314
315 ) // конец внутреннее класса HyMessageListaner
316
317 // HessageDisplayer отображает новее сообщение, добавляй его
318 // в текстовую область messageArea. Этот объект типа Runnable
319 // должен выполняться только в пототе с дмспатчермециеи событий,
320 // поскольку он модифицирует коипотент Swing реального времени.
321 private class HessageDisplayer implements Runnable (
322
323 private String fromUser,-
324 private String messageBody;
325
326 // конструктор MessageDisplayer
327 public HessageDisplayer( String from, String body )
328 (
329 fromUser ■ from;
330 messageBody = body;
331 )
332
333 // отображение сообщения в области messageArea
334 public void run()
335 (
336 // добавление нового сообщения
337 messageArea.append( "\n" + fromOser + "> " +
338 messageBody );
339
340 // перенос курсора в кояео области messageArea, чтобы
341 // новее сообщение было видимо на экрана
342 messageArea.setCaretPosition(
343 messageArea.getText().length 0 );
344 )
345
346 ) // конец внутреннего класса MessageDisplayer
347
348 // DisconnectHandler прослушивает сообщения serverDisconnected
349 // от MessageManager и обновляет пользовательский клтерфейс.
350 private class DisconnectHandler implements DisconnectListener {
351
352 // получение уведомления об отключении
353 public void serverDisconnected( final String message )
354 (
355 // обновление пользовательского интерфейса
356 SwingDtilities.invokeLatar(
357
358 new ВшшаЫеО {
359
360 // обновление действий, полей ввода и строки состояния
361 public void run()
362 (
363 sendACtion.setEnabled( false );
364 disconnectAction.setEnabled( false ).-
365 inputftxea.aetEditable( false );
366 connectAction.setEnabled( true );
367 atatusBar.setText( message );
368 )
369
370 ) // конец внутреннего класса Runnable
371 > ;
372
373 ) // конец гоезюда serverDisconnected
374
375 > // конец внутреннего класса DisconnectHandler
376 )
Рис. 2.23. Класс ClientGUI предоставляет графический пользовательский интерфейс для
клиента Deitel Messenger
Конструктор ClientGUI (строки 41-141) создает и размещает различные
компоненты пользовательского интерфейса. Конструктор принимает в качестве
параметра объект MessageManager, который реализует основные взаимодействия.
Внутренний класс Window Adapter (строки 119-133) обеспечивает отсоединение Messa-
geManager от сервера ChatServer (строка 126), когда пользователь закрывает окно
приложения.
Внутренний класс ConnectAction (строки 144-205) представляет собой
реализацию действия Action для соединения с сервером Deitel Messenger. В строках
170-174 вызывается метод connect интерфейса MessageManager и регистрируется
слушатель DisconnectListener для получения уведомлений serverDisconnected.
В строках 177-186 у пользователя запрашивается имя. которое будет
использоваться в сеансе интерактивного общения, и обновляются компоненты пользовательского
интерфейса, чтобы дать возможность пользователю отправлять сообщения и
отключаться от сервера Deitel Messenger. В строках 188-189 вызывается метод send-
Message интерфейса MessageManager для отправки сообщения Chat Message,
которое объявляет об участии пользователя в сеансе интерактивного общения.
Удаленный вызов методов 73
Внутренний класс Disconnect Action (строки 211-258) является реализацией
интерфейса Action для отключения объекта Mess age Manager от сервера Deitel
Messenger. В строках 231-282 посылается сообщение С hat Message, чтобы
объявить о выходе пользователя из сеанса интерактивного общения. В строке 236
вызывается метод disconnect интерфейса MessageManager для отключения от
сервера. В строках 239-248 обновляются компоненты пользовательского интерфейса,
чтобы запретить использование области ввода сообщений input Area и отобразить
сообщение в строке состояния.
Внутренний класс sendAction (строки 261-299) является реализацией
интерфейса Action для отправки сообщений на сервер. В строках 282-283 вызывается
метод send Message интерфейса MessageManager и передается содержимое области
ввода сообщений input Area и имя пользователя userName в качестве параметров.
Экземпляр внутреннего класса MyMessageListener (строки 304-315)
прослушивает входящие сообщения Chat Message. Когда Msesage Manager принимает
новое сообщение ChatMessage с сервера, MessageManager вызывает метод message-
Received (строки 307-313). В строках 311-312 вызывается статический метод
invokeLater класса SwingUtilities с параметром MessageDisplayer для
отображения нового сообщения.
Внутренний класс MessageDisplayer (строки 321-346) является реализацией
интерфейса Rannable, который добавляет новое сообщение в текстовую область
mess age Area типа JTextArea для отображения этого сообщения пользователю.
В строках 337-338 текст сообщения и имя отправителя добавляются в область
mess age Area, а в строках 342-343 курсор перемещается в конец области
сообщения message Are а.
Экземпляр внутреннего класса Disconnect Handler (строки 350-375) принимает
уведомления serverDisconnected от объекта MessageManager при отключении от
сервера. В строках 356—371 обновляются компоненты пользовательского
интерфейса, чтобы указать, что сервер отсоединен.
Класс DeitelMessenger (рис. 2.24) запускает клиентское приложение с
помощью классов CilentGUI и R MI MessageManager. В строке 18 вызывается метод
set Security Manager класса System для задания класса RMISecnrityManager для
клиентского приложения. Этот менеджер безопасности необходим клиенту для
динамической загрузки заглушки объекта ChatServer. О динамической загрузке
классов мы поговорим в разделе 2.6.3. Если пользователь не указав имени для
сервера ChatServer, в строке 24 создается объект RMIMessageManager. который
соединяется с сервером, выполняющемся на локальной машине localhost. В
строке 26 создается объект RMIMessageManager, который соединяется с
компьютером, имеющим предоставленное пользователем имя. В строках 29-32 создают
пользовательский интерфейс Client GUI для RMIMessageManager, который
отображается пользователю.
1 // DeitalMeasenger.jav«
2 // DeitelMessenger использует классы ClientGDI и RMXMessageManager
3 // для реализации клиента клтерактинного общения на баэе RHI.
4 package com.deitel.messenger.rmi.client;
5
6 II Набор базовых пакетов Java
7 import Java.rmi.KMISecurityManager;
8
9 // Пакеты Deitel
10 import com.deitel.messenger.*;
11
12 public class DeitelMessenger (
13
14 // запуск приложения DeitelMessenger
74
Глава 2
15 public static void mein ( String args[) ) thross Exception
1« (
17 // запуск менеджера RMXSecurityManager
IB System.setSecurityManager( пей RHI8«curityMmnager() );
19
20 MaesageManager messageManager;
21
22 // создание нового объект* DeitelHessenger
23 if ( args.langth «- 0 )
24 messageMasager ■= new RMXKesaageKanager( "localhost" );
25 else
26 messageManager ■ new RMIJtessageManager ( arga[ 0 ] );
27
28 // окончание коифкрурнронання окна и отображение его
29 ClientGUl olientGUI = new ClientGUI( messageManager );
30 clientSOZ.packO ;
31 clientOTI.setRasizable( false );
32 clientGUi.eetvisible( true );
33 }
_«J
Рис. 2.24. Класс DeitelMessenger запускает клиент чата с помощью классов ClientGUI
и RMIMessageManager
2.6.3. Выполнение серверного и клиентского приложений
Deitel Messenger
Для выполнения серверного и клиентского приложений Deitel Messenger
требуется несколько шагов. В дополнение к реестру RMI для приложения RMI, которое
использует активируемые объекты, требуется деыон активации RMI (rmid). Демон
активации RMI представляет собой серверный процесс, который управляет
регистрацией, активацией и девактивацией удалеивых объектов Active table.
Чтобы начать, запустите реестр RMI, выполнив команду
rmi registry
из командной строки. Проверьте, чтобы файл заглушки для удаленного объекта
ChatServer (ChatServerlmplStub.class) не находился в каталоге реестра RMI,
задаваемого в CLASSPATH, так как это не позволит осуществить динамическую
загрузку класса. Далее запустите демон активации RMI, выполнив команду
rmid -J -Djava.security.policyermid.policy
где nnid.policy — полный путь к файлу политики, представлеивоыу на рис. 2.25.
Этот файл политики дает возможность группе активации ActivationGroup, в
которой выполняется оервер ChatServer, задавать файл C:\activationGronp.pollcy в
качестве файла политики для виртуальной машины для Activation Group. Если вы
помещаете файл activationGroup.policy в каталог, отличный от С:\, не забудьте
модифицировать файл rmid-policy для задания соответствующего местоположения.
1 // разрешение ActivationGroup задавать файл C:\activationGroup.policy
2 // в качестве политики безопасности для виртуальной машины
3 grant (
4 permission com.sun.rmi.rmid.ExecOptionPermission
5 "-Djava. security.policy=file:///C: /activationGroup.policy" ,-
6 >■-
Рис. 2.25. Файл политики для демона активации RMI
Удаленный вызов методов 75
Динамическая загрузка классов дает возможность программам на Java
загружать по сети классы, которые отсутствуют в локальном пути, задаваемом
переменной CLASSPATH. Это особенно полезно для RMI-приложений, позволяющих
клиентам динамически загружать файлы заглушек. Когда объект RMI задает системное
свойство Java,nnl.server.codebase, реестр RMI добавляет аннотацию к удаленным
ссылкам этого объекта. Эта аннотация задает базу кодов, из которой клиенты могут
загружать любые необходимые классы. Эти классы могут включать заглушку для
удаленного объекта и другие классы. Эти файлы .class должны быть доступны для
загрузки с сервера HTTP. Sun предоставляет сервер HTTP, который может быть
использован для целей тестирования. Он может быть загружен по адресу
java.sun.com/products/jdk/rmi/clags-server.zip
Извлеките файлы из архива class-server.zip и прочтите имеющиеся инструкции по
запуску сервера HTTP. В таблице на рис. 2.26 приведены файлы, которые должны
быть включены в каталог загрузки сервера HTTP. Например, если каталогом
загрузки сервера HTTP является C:\classes, скопируйте структуру каталогов и
файлы .class, приведенные на рис. 2.26, в C:\classes. Прежде чем продолжить,
запустите сервер HTTP.
Каталог | Имя файла
com\deital\messanger\rBi±\server\
Chatsaever.class
ChatServerln^l.class
ChatServerIn^l$l.class
ChatServerInpl_Stub.class
StoppableChatServer.class
com\de i te1\messenger\rmi\client\
ChatClient.class
RMIMessageManager Stub.class
com\delte1\messenger\rmi\
|ChatMessaga.class
Рис. 2.26. Список файлов, помещаемых з каталог загрузки сервера HTTP
Далее, выполните приложение Chat ServerAdm i ni s trator для запуска
активируемого удаленного объекта, воспользовавшись командой
Java -Djava.security.policy=administrator.policy
-Djava.rmi.server.codebase=http://ымя_компыотера:порт
com.daitel.messenger.rmi.server.ChatServerAdministrator
start
где administrator, policy — это полный путь к файлу политики, представленному на
рис. 2.27, имя_компьютера — имя компьютера или его IP-адрес, на котором выпол-
няется сервер HTTP, а порт — номер порта, который использует сервер HTTP. Реестр
RMI будет аннотировать каждую удаленную ссылку, которую он возвращает с этой
базой кодов. Файл политики должен разрешить объекту ChatServerAdministrator
соединяться с портом 1098 на локальной машине, который является портом,
используемым демоном активации RMI. Файл политики также должен разрешать
приложению ChatServerAdiniiiistrator доступ к порту, на котором выполняется Web-сервер.
Строки 4-5 листинга на рис. 2.27 задают, что приложение ChatServerAdministrator
76
Глава 2
может осуществлять доступ ко всем портам, начиная с порта 1024 компьютера
и выше. Не забудьте заменить имякомпъютера на соответствующее доменное имя
или IP-адрес компьютера, на котором выполняется Web-сервер и демон активации
RMI. Приложение Chat Server Administrator также требует полномочий типа >а-
vaJang.RnntimePermlsston для setFactory, которое позволяет классу Activation-
Group устанавливать менеджер безопасности SecurityManager.
1 // разрешение ChatServerAdministrator соединяться
2 // с демоном активации
3 grant (
4 permission java.net.SocketPermission "hostname:1024-",
5 "connect, accept, resolve";
6
7 permission java.lang.RuntxmePenniaaion "setFactory";
8 );
Рис. 2.27. Файл политики для приложения ChatServerAdministrator
Приложение С hat Server Administrator регистрирует группу активации
Activation Group для активируемого объекта Chat Server, а затем осуществляет выход.
Клиенты после этого могут осуществлять доступ к объекту ChatServer, получая
удаленную ссылку на объект ChatServer из реестра RMI и вызывая методы по этой
удаленной ссылке. Имейте в виду, что сервер ChatServer не начнет выполняться
до тех пор, пока клиент не вызовет метод удаленного объекта ChatServer.
В этот момент система активации активирует группу ActivationGroop сервера
ChatServer. Чтобы запустить клиент для ChatServer, введите следующую
команду в командной строке:
java -DJava.security.policy=client.policy
com. deital .messenger, rmi. client.DeitelMeseenger
где client.policy — файл политики, представленный на рис. 2.28. Этот файл
политики позволяет клиенту соединяться, принимать соединения и разрешать
соединения с указанным компьютером через порты с номерами 1024 и выше. Напомним,
что клиент сам является удаленным объектом, поэтому клиент должен быть
способен принимать входящие сетевые соединения от сервера ChatServer. He забудьте
заменить hostname на имя компьютера или IP-адрес компьютера, на котором
выполняется сервер ChatServer,
1 // разрешение клиенту соединяться с сетевыми ресурсами
2 // кокпьотера по портам, номера которых превышает 1024
3 grant (
4 permission Java.net.SocketPennission "hostname:1024-",
5 "connect, accept, resolve'1.-
6 >■■ ■
Рис. 2.28. Файл политики для клиента DeitelMessenger
На рис. 2,29 представлен пример диалога в Deitel Messenger, Обратите
внимание, что элементы пользовательского интерфейса надлежащим образом отражают
текущее состояние соединения, — когда клиент отсоединен, разрешено только
действие ConnectAction (установить соединение), После соединения клиента
становятся доступными действие DisconnectAction (разорвать соединение), область
JTextArea для ввода сообщения и действие SendAction (отправить). Заметьте, что
внизу каждого окна отображается сообщение Java Applet Window. Виртуальная
машина помещает это сообщение в окно, поскольку приложение выполняется с
ограничениями по безопасности.
Удаленный вызов методов
—Щ
ij»n^tfc«wA»" j^r
2|Г^*^^^^^Т"и'^
Рис, 2,29. Образец диалога с помощью приложения Deitel Messenger
78
Глава 2
2.7. Ресурсы в Internet и во Всемирной паутине
java. sun.соя/pxoducta/jdk/mi/iitdax.html
Основная страница Sun no технологии Remote Method Invocation (RMI), где
предоставлены ссылки на технические статьи, документацию и другие ресурсы.
Java. sun. соя/j2e*/l ■ 3/docs/guide/rai/index. html
Руководство по RMI от Sun, которое содержит ссылки на учебный материал по
построению активируемых удаленных объектов и другие полезные ресурсы.
*пш. jguru.coe/f»q/hoee. jsp?topic«RMI
Часто задаваемые вопросы и ответы па них (FAQ) на сайте jGuru no RMI, а также сояе-
ты разработчикам по применению RMI.
«ют. j>vawoxld.cc«/javsvOEld/copic*lind*x/jw-tt-XBi.h£ail
Список статей JavaWorld, имеющих отношение к RMI. Статьи затрагивают такие
темы, как активируемые объекты RMI, интеграцию RMI и CORBA, другие связанные
с RMI технологии, такие как Jini.
Резюме
• RMI дает возможность объектам Java, выполняющимся на разных компьютерах или
в разных процессах, взаимодействовать друг с другом посредством удаленных вызовов
методов. Такие вызовы методов представляются программисту такими же, как операции
над объектами в той же программе.
• Технология RMI основана на более ранней тех кол огни процедурного программирования
под названием RPC (Remote Procedure Calls), разработанной в 80-е годы.
• RMI дает возможность программам на Java передавать объекты Java с помощью
механизма сериализацни объектов Java. Программисту не нужно заботиться о том, как дакные
передаются через сеть.
• Для взаимодействий между приложениями Java и приложениями, разработанныыш на
других языках программирования, можно воспользоваться языком Java IDL (введенном
в Java 1.2) или RMI-ПОР. Java IDL и RMI-ПОР дают возможность приложениям и аппле-
там, написанным на Java, взаимодействовать с объектами, написанными на любом
языке, поддерживающем архитектуру CORBA (Common Object Request Broker Architecture).
• Построение распределенной системы RMI осуществляется в четыре этана: I) определение
удаленного интерфейса, 2) определение реализации удаленного объекта, 3) определение
клиентского приложения, которое использует удаленный объект, и 4) компиляция и
выполнение удаленного объекта и клиента.
• Для создания удаленного интерфейса определите интерфейс, который расширяет
интерфейс javo.rmt.Rrmote. Интерфейс Remote является тегирующим интерфейсом — он не
объявляет каких-либо методов, и, следовательно, не несет нагрузки, связанной с реалнза-
• Объект класса, который реализует интерфейс Remote, напрямую или косвенно является
удаленным объектом и допускает доступ С соответствующими полномочиями,
определяемыми системой безопесности, из любой виртуальной машавы Java, имеющей соединение
с компьютером, ва котором выполняется удаленный объект.
• Каждый удаленный метод должен быть объявлен в интерфейсе, который расширяет
интерфейс java.rmi.Remote. Удаленный объект должен ре&лиэовывать все методы,
объявленные в его удаленном интерфейсе.
• Распределенное приложение RMI должно экспортировать объект класса, который
реализует интерфейс Remote, чтобы сделать удаленный объект доступным для приема
удаленных вызовов методов.
• Каждый метод в интерфейсе Remote должен иметь предложение throws, которое
указывает, что метод может воебуждать исключение RemoteExceptlon. Исключение Remote-
Exeeptlon указывает ив проблему при коммуникационном взаимодействии с удаленным
объектом.
• RMI использует механизм сериализацни Java по умолчанию для передачи параметров
метода и возврата значений через сеть. В этой связи все параметры методов и возвращаемые
значения должны иметь тип Seriallsable или один из примитивных типов.
Удаленный вызов методов
79
• Класс UnicastRemoteObject предоставляет базовые функциональные возможности,
необходимые для всех удаленных объектов. В частности, его конструктор экспортирует
объект, чтобы он мог принимать удаленные вызовы.
• Экспорт удаленного объекта дает возможность этому объекту ожидать соединений с
клиентами через анонимный номер порта (т.е. порт, выбранный компьютером, на котором
выполняется удаленный объект). RMI абстрагируется от деталей коммуникационного
взаимодействия, чтобы программист мог работать с простыми вызовами методов.
• Конструкторы для класса UnicastRemoteObject позволяют программисту задавать
информацию об удаленном объекте, включая номер порта, через который экспортируется
удаленный объект. Все конструкторы UnicastRemoteObject возбуждают исключение
RemoteException.
• Про грамм а-утилита г mi registry управляет реестром удаленных объектов и входит в
состав пакета SDK J2SE. Нокер порта по умолчанию для реестра RMI — 1099.
• Метод lookup осуществляет соединение с реестром RMI к возвращает ссылку типа Remote
на удаленный объект. Имейте в виду, что клиенты обращаются к удаленным объектам
только через удаленные интерфейсы этих объектов.
• Удаленная ссылка представляется па клиенте объектом-заглушкой. Заглушки дают
возможность клиентам вызывать методы удаленных объектов. Объекты-заглушки
принимают удаленные вызовы и передают эти вызовы системе RMI, которая выполняет сетевые
взаимодействия, которые позволяют клиенту связываться с удаленным объектом.
• Уровень RMI ответственен за сетевые соединения с удаленным объектом, чтобы
обращения к удаленным объектам были прозрачными для клиента. RMI обслуживает основные
коммуникационные взаимодействия с удаленным объектом, передает параметры и
возвращаемые значения. Пареметры и возвращаемые значения для удаленных методов
должны иметь описатель SerializeЫе.
• Утилита nnic компилирует класс удаленного объекта для формирования
класса-заглушки. Класс-заглушка переадресует вызовы методов на уровень RMI, который выполняет
сетевые взаимодействия, необходимые для вызовов методов удаленного объекта.
• Стандартные объекты RMI, экспортируемые как объекты UnicastRemoteObject, должны
непрерывно выполняться на сервере, чтобы обслуживать клиентские запросы. Объекты
RMI, которые расширяют класс java.nni.activation.Activatable, могут активироваться
когда клиент вызывает один из методов удаленного объекта.
• Демон активация RMI (rmid) представляет собой серверный процесс, который дает
возможность переводить в активное состояние активируемые объекты, когда клиент
вызывает удаленные методы этих объектов.
• Активируемые удаленные объекты способны восстанавливаться после сбоев сервера,
поскольку удаленные ссылки на активируемые объекты неизменны, — когда сервер
перезапускается, демон активации RMI сохраняет удаленные ссылки, поэтому клиенты могут
продолжать использовать удаленный объект.
• Механизм активации RMI требует, чтобы активируемые объекты предоставляли
конструктор, который принимает в качестве параметра идентификатор ActivationID и объект
MarshalledObject. Когда демон активации активирует удаленный объект этого классе, он
вызывает этот конструктор активации. Параметр ActivationID задает уникальный
идентификатор удаленного объекта.
• Класс MarsballedObject представляет собой клаос-обертку, который содержит сериализо-
ванный объект для передачи через RMI. Объект MarsballedObject, передаваемый
конструктору активации, может содержать специфичную для приложения
инициализирующую информацию, такую как имя, под которым демон активации регистрирует
удаленный объект.
• Активируемые объекты RM1 выполняются как часть класса ActivationGroup (накат
java.rmi.activation). Демон активации RMI — серверный процесс, который управляет
активируемыми объектами, — запускает новую виртуальную машину для такого объекта
ActivationGroup.
• Класс Activation Group Desc определяет информацию о конфигурации для группы
ActivationGroup. Первый параметр, передаваемый конструктору Activation OroupDeec,
представляет собой ссылку на объект типа Properties, который содержит замещающие
значения для системных свойств в виртуальной машине для объекта ActivationGroup. Второй
параметр представляет собой ссылку на объект ActivationGronpDcac .Comma nd Rn vlron -
ment, дающий возможность объекту ActivationGroup настраивать команды, которые
демон активации исполняет при запуске виртуальной машины для группы Activation-
• Номер воплощения группы ActlvationGroup идентифицирует различные экземпляры
одного и того же объекта Act i vat ion Group. Каждый раз, когда демон активации активирует
группу ActivationGnmp, он инкрементирует номер воплощения.
• Клаос ActivationDesc задает информацию о конфигурации для конкретного
активируемого удаленного объекта. Первый параметр, передаваемый конструктору ActivationDesc.
задает имя класса, который реализует активируемый удаленный объект. Второй
параметр задает базу кодов, которая содержит файлы классов удаленного объекта. Последний
параметр представляет собой осылку на объект MarahalledObject, который содержит
инициализирующую информацию для удаленного объекта.
■ Метод register класса Activatable принимает в качестве параметра объект ActivationDesc
для активируемого объекта и возвращает ссылку на заглушку удаленного объекта.
• Динамическая загрузка классов из сети дает всеможиость программам на Java загружать
классы, отсутствующие в локальном пути, задаваемом переменной окружения CLASS-
PATH. Это особенно полезно для RMI-приложений, поскольку позволяет клиентам
динамически загружать файлы-заглушки.
• Когда объект RM3 задает системное свойство java.rmi.aerver.codebase, реестр RMI
добавляет аннотацию к удаленным ссылкам объекта, задающую базу кодов, из которой
клиенты могут загружать необходимые классы. Загружаемые файлы .class должны быть
доступны на сервере HTTP-
Терминология
Activatable, класс (пакет LietCellKenderer, интерфейс
Java.nni.activation) LocateRegistry, класс
activatable remote object — активируемый marshalling of data — маршалинг данных
удаленный объект MarehalledObject, класс
activation daemon — демон активизации rebind, метод класса Naming
activation descriptor — дескриптор актива- Registry, класс
ции remote interface — удаленный интерфейс
activation group deecriptor — дескриптор Remote, интерфейс (пакет java.rmi)
группы вктизации remote method — удаленный метод
Activation Group, класс Remote Method Invocation (RMI), техноло-
Activat ion Group Desc, класс гня удаленного вызова методов
ActivetionGroupDesc.Command.Environ- remote object — удаленный объект
ment, класс remote object implementation — реализация
ActivetionlD, класс удаленного объекта
Activation System, интерфейс Remote Procedure Call (RFC), технология
Adapter design pattern — паттерн проект и- удаленного вызова процедур
рования Adapter remote reference — удаленная ссылка
anonymous port number — анонимный но- Re mot «Except ion, класс (пакет java.rmi)
мер порта RMI registry — реестр RMI
bind, метод класса Naming rmic, компилятор
createRegistry, метод класса LocateRegistry nnid, утилита
distributed computing — распределенные rmiregistry, утилита
вычисления RMISecurityManager, класс
export — экспорт stub class — класс-заглушка
exportObject, метод класса tagging interface — тегирующий интерфейс
UnicastRemoteObject UnicaetKemoteObjset, клаос (пакет
Interface Definition Language (IDL), язык java.rmi^erver)
определения интерфейсов
Упражнения для самоконтроля
2.1. Заполните пропуски в следующих высказываниях:
a) Класс удаленного объекта должен быть откомпилирован с помощью ,
чтобы сформировать клаос-заглушку.
b) Технология RMI основана на схожей технологии для процедурного программ и рова-
Удаленный вызов методов
81
c) Клиенты используют метод класса Naming для получения удаленной
ссылки на удаленный объект.
d) Чтобы создать удаленный интерфейс, определите интерфейс, который расширяет
интерфейс пакета .
e) Метод няи класса Naming связывает удаленный объект
с реестром RMI.
f) Удаленные объекты обычно расширяют класс , который предосталляет
основные функциональные всеможности, необходимые для асех удаленных объектов.
g) Удаленные объекты используют _______ и для нахождения реестра
RMI для регистрации в качестве удаленных сервисов. Клиенты используют их для
нахождения сервиса.
h) Номрром порта по умолчанию для реестра RMI является .
i) Интерфейс Remote представляет собой -
j) дает возможность объектам Java, выполняющимся иа отдельных
компьютерам (или, возможно, на одном компьютере), взаимодействовать друг с другом
посредством удаленных вызозов методов.
2.2. Ответьте, является ли каждое из следующих высказываний истинным или ложным.
Бели высказывание ложно, объясните, почему.
a) Если не запустить реестр RMI перед попыткой салзать удаленный объект с
реестром, будет возбуждено исключение RnutimeExceptioa, что не позволит соединиться
с реестром.
b) Каждый удаленный метод должен быть частью интерфейса, который расширяет
интерфейс jav a. rmi. Remote.
c) Компилятор gtubcompiler создает класс-заглушку, который осуществляет сетевые
взаимодействия, псоволяющие клиенту соединяться с оервером и использовать методы
удаленного объекта.
d) Класс UnicaatRemoteObject предоставляет основные функциональные
возможности, необходимые для удаленных объектов.
e) Объект класса, который реализует интерфейс Serialiiable, может быть
зарегистрирован как удаленный объект н принимать удаленные вызовы метода.
f) Все методы в интерфейсе Remote должны иметь предложение throws, указывающее
на возможное исключение Remote Exemption,
g) Клиенты RMI пред полегают, что они могут соединяться с портом 80 на компьютере
сервера при попытке найти удаленный объект через реестр RMI.
h) После того как удаленный объект связывается с реестром RMI с помощью метода
bind или rebind класса Naming, клиент может отыскивать удаленный объект С
помощью метода looknp класса Naming.
i) Метод find класса Naming взаимодействует с реестром RMI, чтобы помочь клиенту
получить ссылку на удаленный объект и воспользоваться сервисами удаленного объекта.
Ответы на упражнения для самоконтроля
2.1. а) компилятора nnic. Ь) RPC. с) looknp. d) Remote, Java.rmi. e) bind, rebind. f)
Unices t Rem о teObject. g) имя компьютера (IP-адрес), порт, h) 1099. i) тегирующий
интерфейс, j) RMI.
2.2. а) Ложно. Это приводит к исключению java.nni-CoiuiectException.
b) Истинно.
c) Ложно. Класс-заглушку создает компилятор rmic.
d) Истинно.
e) Ложно. Объект класса, который реализует интерфейс java.rmi.Remote, может быть
зарегистрирован как удаленный объект и принимать удаленные вызовы методов.
f) Истинно.
g) Ложно. Клиенты RMI используют по умолчанию порт 1099. Порт 80 используется
по умолчанию Web-сервером,
h) Истинно.
i) Ложно. Метод looknp взаимодействует с реестром RMI, чтобы помочь клиенту
получить ссылку на удаленный объект.
82
Глава 2
Упражнения
2.3. Текущая реализация класса WeatherServicelmpl загружает информацию о погоде
только один раз. Модифицируйте класс WeatherServicelmpl, чтобы получать
информацию о погоде от службы National Weather Service два раза а день.
2.4. Модифицируйте интерфейс Weather Service, включив поддержку для получения про-
гисеа на текущий день и па следующий день. Изучите Web-страницу Traveler's
Forecast
http://iwin.nva.noaB.gov/iwin/us/tcavel.er.btml
2.5. Посетите Web-сайт Национальной службы погоды (NWS) и обратите внимание па
формат каждой строки информации. Затем модифицируйте клаос WeatherServicelmpl,
чтобы реализовать новые функции интерфейса. Наконец, модифицируйте класс
Weather ServiceClient, чтобы лать возможность пользователю выбирать прогноз погоды на
любой день. Модифицируйте классы поддержки WeatherBean и Weather Item, чтобы
обеспечить поддержку изменений в классах WeatherServicelmpl я WeatherService-
Client.
2.6. (Проект- Прогнсе погоды для вашего штата.) Иа Web-сайте Национальной службы
погоды NWS содержится большой объем информации. Изучите следующие Web-страннцьг.
http: //iwin. nwe. noaa. gov/
http: //iwin .nvi. noaa. gov/ iwin/ textvers Ion/main, html
и создайте полноценный сервер прогноза погоды для своз го штата. Обеспечьте
повторное использование разработанных вами классов.
2.7. (Проект. Прогноз погоды для штата.) Модифицируйте упражнение 2.6, чтобы дать
возможность пользователю выбирать информацию о погоде для любого штата.
[Замечание. Для некоторых штатов формат прогноза погоды отличается от стандартного
формата. Вы должны позволить пользователю выбирать только штаты, прогноз для
которых имеет стандартный формат.
2.8. {Международная версия.) Если в вашей стране имеется схожая служба погоды на базе
Web, предоставьте другую реализацию WeatherServicelmpl с тем же удаленным
интерфейсом WeatberService (рис. 2.1). Сервер должен возвращать информацию о погоде
для крупнейших городов вашей страны.
2.9. {Сервер удаленной телефонной книги.) Создайте сервер удаленной телефонной книги,
который содержит файл имен и телефонных номеров. Определите интерфейс РЬопе-
BookServer со следующими методами:
public PhoneBookEntry [] g«tPhooaBook()
public void addEntry( PhoMBookEntxy entry )
public void modifyHntry( PhoneBookEntry entry )
public void d*l*teXntry{ PhoneBookEntry entry )
Ссодайте активируемый удаленный объект класса PhoneBookServerlmpl, который
реализует интерфейс PhoneBookServer. Класс PhoneBookEntry должен содержать
строковые переменные экземпляра, к вторые представляют имя, фамилию и номер те-
лефова для одного лица. Класе также должен предоставлять соответствующие методы
set/get и выполнить проверку правильности формата телефонного номера. Напомним,
что класс PhoneBookEntry также должен реалкзовывать интерфейс Serializable,
чтобы RMI мог сериализовывать объекты этого классе.
2.10. Класс PhoneBookClient должен предоставлять пользовательский интерфейс, который
дает возможность пользователю осуществлять прокрутку записей, добавлять новые
записи, модифицировать существующие записи и удалять существующие за лиги.
Клиент и сервер должны обеспечивать надлежащую обработку ошибок (например, клиент
не может модифицировать несуществующую запись.
Jini
Цели
• Разобраться в технологии Jini.
• Научиться идентифицировать
основные компоненты
в системе, построенной на
основе Jini.
• Научиться применять сервисы
Jini и регистрировать эти
сервисы с помощью служб
поиска Jini.
• Научиться создавать клиенты
Jini.
• Научиться использовать
вспомогательные классы Jini
для упрощения реализации
сервисов.
Настоящее познавательное
путешествие заключается не
в открытии новых земель, а
в том, что у вас появляется
новый взгляд на мир.
Марсель Пруст
...У лета срок владенья
слишком краток.
Уильям Шекспир
Ибо если добродетель
с красотой соединить.
Как расцветился б лик
божественной сей формы.
Мэттыо Прайор
..Лжя твое называть день-деньской
Перед болотом восхищенным.
Эмили Дикинсон
84
Глава 3
3.1. Введение
Многие сетевые устройства предоставляют сервисы клиентам сети. Например,
сетевой принтер предоставляет услуги по печати документов клиентам, позволяя им
совместно использовать принтер. Аналогично Web-сервер предоставляет сервис,
давая возможность клиентам осуществлять доступ к документам через сеть. Мы
можем распространить эту идею не только на компьютерные сети, но и на бытовые
устройства и системы. Например, когда вы подъезжаете к дому, ваш автомобиль
может воспользоваться беспроводной сетью, чтобы дать указание системе управления
освещением включить свет у гаража. Каждый из упомянутых здесь сервисов имеет
строго определенный интерфейс. Сервис сетевой печати предоставляет интерфейс,
который дает возможность приложениям печатать документы. Web-сервер
предоставляет интерфейс HTTP, который позволяет Web-браузерам загружать документы
из сети. Сервис освещения гаража предоставляет интерфейс, который дает
возможность другим устройствам в сети включать и выключать свет.
Чтобы воспользоваться сервисом, клиент должен иметь возможность
обнаруживать наличие сервиса и знать интерфейс для взаимодействия с сервисом.
Например, ваш автомобиль должен уметь обнаруживать, что гараж имеет систему
управления освещением, и должен знать интерфейс сервиса для взаимодействия
85
с системой управления освещением. Однако машина не обязана знать, каким
образом реализована система управления освещением.
Jini расширяет RMI (см. гл. 2), чтобы предоставлять в сети сервисы, подобные
упомянутым выше. Сервисы Jini построены по принципу «подключил и
работай» — клиент может обнаруживать сервисы в сети динамически, прозрачно
загружать классы, необходимые этим сервисам, а затем начинать взаимодействовать
с этими сервисами. Jini, подобно RMI, требует, чтобы клиенты знали интерфейс
сервиса, который они собираются использовать. Однако возможность
динамической загрузки классов RMI позволяет клиентам Jini использовать сервисы без
установки специального программного драйвера. Для того чтобы клиенты Jini
обнаруживали и использовали сервисы Jini, должны быть разработаны
стандартизованные интерфейсы для типовых сервисов. Многие такие интерфейсы сейчас уже
разрабатываются. Например, производители принтеров работают над
стандартным интерфейсом для сервисов печати на основе Jini.
В этой главе мы познакомимся с технологией Jini для построения сетевых
сервисов по принципу «подключил и работай*. Для начала мы рассмотрим поисковые
сервисы Jini, которые дают возможность клиентам обнаруживать другие сервисы
в сети. Мы построим простой сервис Jini, который предоставляет информацию
о семинарах, проводимых компанией Deitel & Associates, Inc., а также создадим
простой клиент Jini, который будет использовать этот сервис. В конце главы мы
познакомимся с классами утилит Jini, которые облегчают обнаружение сервисов,
регистрацию сервисов Jini и создание клиентов Jini. Изучив эту главу, вы сможете
создавать простые сервисы Jini и клиентов, которые взаимодействуют с этими
сервисами. [Замечание. Команды для выполнения примеров в этой главе часто
являются довольно длинными. По этой причине мы предоставляем пакетные файлы,
содержащие команды. Эти пакетные файлы можно найти в архиве с примерами
к кинге. Вы можете модифицировать эти пакетные файлы, как указано в тексте,
а затем воспользоваться ими для выполнения программы.]
Новой технологией, которая вызывает большой интерес в компьютерной
индустрии, является создание пиринговых сетевых приложений, которые дают
возможность осуществлять взаимодействие между компьютерами без соединения их
через сервер. В главе 9 мы воспользуемся технологией Jini, представленной в этой
главе, как средством для реализации пиринговых приложений для оперативного
обмена сообщениями.
3.2. Установка Jini
К базовому программному обеспечению, необходимому для функционирования
Jini, относятся Java 2 Standard Edition (J2SE) и Jini Technology Starter Kit. Если
вы собираетесь ранрабатывать коммерческие сервисы Jini и хотели бы
протестировать их на совместимость с платформой Jini, вам также понадобится загрузить
пакет Jini Technology Core Platform Compatibility Kit (JTCK).
Пакет Jini Starter Kit состоит из трех компонентов: Jini Technology Core
Platform (JCP), Jini Technology Extended Platform {JXP) и Jini Software Kit (JSK).
JCP содержит основные интерфейсы и классы Jini. JXP предоставляет
вспомогательные утилиты для реализации сервисов и клиентов Jini. JSK содержит
реализацию сервисов, определенных в JCP и JXP. В состав JSK также входит
реализация технологии JavaSpaces. JavaSpaces будет обсуждаться в главе 4.
Jini можно загрузить с Web-сайта Sun по адресу:
www. aun. com/conmunityBouxcfl/ jini/download, html
86 Глава 3
Чтобы загрузить Jini, необходимо зарегистрироваться. Зарегистрировавшись,
загрузите и разархивнруйте архив JINI-l.l-G-CS.sip с Jini Technology Starter Kit.
На момент написания этой книги актуальной была версия 1,1 Jini.
3.3. Настройка среды Jini
Чтобы откомпилировать и выполнить сервисы и клиенты Jini, в переменную
окружения CLASSPATH необходимо включить JAR-файлы jini-corejar, jini-extjar
и stm-atiLjar. Эти три JAE-файла содержатся в каталоге lib пакета Jini Technology
Starter Kit — они относятся, соответственно, к Jini Technology Core Platform, Jini
Technology Extended Platform и Jini Software Kit. Чтобы задать переменную
окружения CLASSPATH в Windows, введите
set CIASSPATH=c:\jinil-l\lib\jini-oo».jar;e:\jinil-l\lib\
jini-ext.jar,c:\jinil-l\lib\sun-util.jar;%CIASSPATH%;
в командной строке. Чтобы задать переменную окружения CLASSPATH в UNIX,
введите
set CLASSPATH=/jinil-l/lib/jini-co».jar:/jinll-1/lib/
jini-ext.jar:/jinil-1/lib/sun-util.jar:$CIASSPATH:
export CLASSPATH
Имейте в виду, что вам нужно проделать это каждый раз перед использованием
Jini, если только вы не задали постоянное значение переменной CLASSPATH в
вашей системе. Обратитесь к документации на вашу операционную систему за
информацией об задании переменных окружения. Это единственные JAR-файлы
Jini, которые должны быть указаны в переменной CLASSPATH. He включайте
в нее какие-либо другие JAR-файлы из дистрибутива Jini. Если вы извлекли
файлы Jini в другой каталог, не забудьте указать имя этого катвлога.
3.4. Запуск обязательных сервисов
Работа Jini во многом зависит от количества выполняющихся сетевых
сервисов. Jini использует Java и RMI для загрузки ресурсов из сети и для перемещения
объектов Java с одного компьютера на другой. Чтобы воспользоваться этими
возможностями, Jini предоставляет различные сервисы поддержки. В дистрибутив
Jini входят три сервиса, которые должны быть запущены перед исполнением
приложений Jini, включая:
1. Web-сервер, дающий возможность клиентам Jini загружать файлы классов
посредством RMI, чтобы клиенты могли динамически осуществлять доступ
к сервисам Jini.
2. Демон активации RMI (rmid), активирующий инфраструктуру RMI, что
дает возможность клиентам Jini взаимодействовать с сервисами Jini.
Демон активации RMI способствует надлежащему функционированию
сервисов Active table.
3. Сервис поиска для хранения информации об имеющихся сервисах Jini,
дающий возможность клиентам обнаруживать и использовать эти сервисы.
ПВМ Совет по отладке и тестированию 3.1
Web-сервер и демон активации rmid должны быть запущены в любом
порядке до запуска сервиса поиска.
jini
87
Реализация Jini Technology Core Platform включает утилиту StartService для
запуска необходимых сервисов. Прежде чем запустить утилиту StartService,
убедитесь, что переменная окружения CLASSPATH задана в соответствии с
указаниями, приведенными в раздвле 3.3. Чтобы запустить утилиту в Windows, введите
в командной строке:
Java -claespath %CLASSPATH%;c:\jinil-l\lib\jini-example«.jar
cam.son.jini.example.launcher.StartService
На рис, 3,1 представлена утилита StartService. Jini предоставляет базовый
файл свойств для настройки утилиты StartService. Для Windows таким файлом
свойств является jinil-l\examples\Iaimcher\jinill-wm32.propertieB. Чтобы
загрузить этот фал свойств, а меню File, выберите Open Property File и укажите
файл свойств в соответствующем каталоге.
Рис, 3.1. Окно StartService
Настройка Web-cepeepa
Чтобы настроить Web-сервер, выберите панель Webserver. На рис. 3.2
представлены значения для настройки Web-сервера. Если вы установили Jini Starter
Kit в каталог, отличный от C:\jinil-1, не забудьте ввести соответствующее имя
каталога. Обратите внимание на номер порта Port на рис, 3.2, которые понадобится
нам во всех последующих примерах.
Рис. 3.2. Вкладка настройки Web-сервера
Глава 3
Настройка демона активации RMt
Чтобы настроить демон активации RMI (rmid), выберите вкладку RMID. На
рис. 3.3 представлены значения по умолчанию, используемые для настройки
демона активации RMI. Если вы хотите использовать вашу собственную политику или
же добавить другие опции (т.е. номер порта или каталог журнала), то можете
ввести их в параметре Options. Для отделения опций друг от друга используйте одни
пробел. На рис. 3.4 показан пример задания каталога, в который rmid будет
записывать файлы журнала.
Рис. 3.3. Вкладка конфигурирования RM1D
Рис. 3.4. Задание каталога журнала RMID
Настройка сервиса поиска
Чтобы настроить сервис поиска, выберите паиель Reggie. На рис. 3.5
представлены значения параметров настройки. Замените hostname в поле Со debase на
доменное имя или IP-адрес вашего компьютера. Замените номер порта (8081) на
номер порта, который вы указали при настройке Web-сервера (например, 9000). Не
забудьте также задать в поле Log directory каталог, который реально существует
на вашем компьютере, или создайте новый каталог, чтобы избежать исключений
при выполнении сервиса поиска. Значение поля Grenp в этом примере (public)
указывает, что сервис поиска поддерживает любых клиентов в сети. Вы можете
определить другие значения, которые могут быть использованы для ограничения
множества клиентов, поддерживаемых сервисом поиска.
Рис. 3.5. Вкладка Reggie настройки сервиса поиска
щг&з Типичная ошибка программирования 3,1
[^" | Задание имени локального компьютера или использование протокола file:
для поля Codeba.se является ошибкой. Используйте протокол http: и пол
ностыо заданное доменное имя или IP-адрес.
™gn Типичная ошибка программирования 3.2
£ff* Запуск сервиса поиска без создания каталога журнала приведет к возбуж-
LEV—' дению исключения в процессе выполнения. Создайте каталог журнала,
прежде чем запускать сервис поиска.
я™*п Типичная ошибка программирования 3.3
\ЩГ I Создавайте различные каталоги журналов, если выполняются несколько
^" экземпляров сервиса поиска. Если этого не сделать, при попытке запус
тишь сервис поиска может быть возбуждено исключение с сообщением,
что каталог уже существует.
Запуск обязательных сервисов
Чтобы запустить обязательные сервисы, выберите вкладку Run в окне Start-
Service (рис. 3.6). Щелкните иа кнопке Start RMID, чтобы запустить демон
активации RMI. Щелкните на кнопке Start Webserver, чтобы запустить Web-сервер.
Щелкните на кнопке Start Reggie, чтобы залустить сервис поиска. Помните, что
демон активации RMI и Web-сервер следует запускать до залуска сервиса п
3.5. Выполнение LookupBrowser Jini
После запуска перечисленных выше сервисов воспользуйтесь средством
LookupBrowser, чтобы их протестировать. Следующая команда запускает Lookup-
Browser в Windows:
Java -cp c:\jinjl_l\lib\jini-exafflple8.jar
-D Java, security.policy=c A jinil_l\«ai4>le\broweer\policY
-Djava.rmi.server.codebase=http://хост:порт/
jini-«xamples-dl.jar
com.sun.jini.example.browser-Browser
где хост — имя или IP-адрес компьютера, на котором выполняется Web-сервер,
а порт — номер порта для Web-сервера (например, 9000).
90
Глава 3
Рис. 3.6. Вкладка Run для запуска и останова базовых сервисов Jim
Можно запустить LookupBroweer с помощью утилиты StartService. Выберите
вкладку LookupBroweer. Замените хост на имя хоста или ГР-адрес компьютера,
на котором выполняется Web-сервер. Измените номер порта, если вы указали
другой номер порта при запуске Web-сервера. Затем перейдите к вкладке Ron и
щелкните на кнопке Start LookupBroweer. На рис. 3.7 показана вкладка Lookup-
Browser в окне StartService.
Рис. 3.7. Вкладка конфигурирования LookupBrowser
На рис. 3.8 показан результат выполнения LooknpBrowBer. Как видно из
рисунка, rmid зарегистрировал одни сервис поиска. В действительности для
нахождения сервиса поиска используется протокол обнаружения. Если вы запустите два
сервиса поиска, то увидите "2 registrars, not selected" ("2 регистратора, не
выбрано"). Количество найденных регистраторов равно числу сервисов поиска. Если вы
видите "no registrar to eelect" ("яет регистраторов для выбора"),
предшествующая настройка была выполнена некорректно. В этом случае проверьте, запущен ли
сервис поиска, а затем проверьте, правильно ли задана база кодов Codebase на
вкладке Reggis.
Если вы щелкните на меню Regiatrar, то увидите имя хоста (или IP-адрес)
и номер порта, на котором зарегистрирован сервис поиска. На рис. 3.9 именем
хоста является DRAGONFLY. Поскольку был использозан номер порта по
умолчанию, на копии экрана номер порта не отображен.
Рис. Э.8. Окно приложения LookupBrowser
3.6. Обнаружение
Сервис поиска Jini является сердцевиной технологии Jini. Процесс
нахождения сервисов поиска и получения ссылок на них называется обнаружением.
Сервис регистрирует себя с помощью одного или нескольких поисковых сервисов,
чтобы сделать себя доступным для клиентов. Для этого сервисы должны, прежде
всего, обнаружить сервисы поиска. Клиенты ищут сервисы поиска, чтобы определить
местонахождение нужных им сервисов. Для этого клиенты сначала должны
обнаружить сервисы поиска. Итак, обнаружение — типичная задача и для сервисов,
и для клиентов.
Обнаружение отличает технологию Jini от RMI. В RMI вам нужно заранее
знать, где зарегистрировать объект. В Jini вам не нужно знать «где* — нужно
лишь знать «как*. Процесс обнаружения определяет «где», но скрывает детали от
разработчика. Обнаружение может быть либо обнаружением с однонаправленным
вещанием (unicast discovery), либо обнаружением с групповым вещанием
(multicast discovery).
3.6.1. Обнаружение с однонаправленным вещанием
Обнаружение с однонаправленным вещанием (локаторное вещание), дает
возможность сервису Jini или клиенту обнаруживать сервисы на заданном хосте.
Сервис Jint или клиент отправляет запрос на обнаружение компьютеру, который
отвечает удаленной ссылкой на сервис поиске, выполняющийся на этом компьютере
на заданном порту.
Приложение, представленное на рис. 3.10, демонстрирует обнаружение с
однонаправленным вещанием. Класс UnicastDiscovery использует класс net.jini.co-
re.diecovery.LookupLocator длн обааружевяя с однонаправленным вещанием.
В строках 17-18 импортируется класс LooknpLocator для обнаружения сервисов
поиска и интерфейс ServiceRegistrar (пакет net.jini.core. lookup), который пред-
ставляет сервис поиска. Конструктор UnicastDiscovery (строки 29-52) создает
кнопку discoverButton типа JButton и текстовое поле oatputArea типа JText-
Агеа. Когда пользователь щелкает на кнопке discoverButton, в строке 43
вызывается метод discoverLooknpServices, который отображает информацию об
обнаруженных сервисах поиска в текстовой области output Area.
1 // DnicastDiscovery.Java
2 // DnicastDiscovery - приложение, демокстрируоцее обнаружение
3 // сервис* помеха Jini для известного хоста.
4 package com.daitel.advjhtpl.jini.discovery;
5
6 // Набор базовых пакетов Java
7 import java.nai.*;
8 import java.net.*;
9 import java.io.*;
10 import java.avt.*;
11 import Java.awt.event.*;
12
13 // Пакета расширений Java
14 import javax.swing.*;
15
16 // Набор базовых пакетов Jini
17 import net.jini.core.discovery.LookupLocator;
18 import net.jini,core.lookup.ServiceRegistrar;
19
20 public class UnicestDisoovery extends JFrame {
21
22 private JTextArea outputArea = new JTextArea( 10, 20 ):
23 private JButton discoverButton;
[ обнаружения сере*
private String hostname;
// конструктор DnicastDiscovery
public DnicastDiscovery( String host )
1
super( "DnicastDiscovery Output" );
hostname = host; // задание имени хоста для обнаружения
// создание кнопки JButton для обнаружения сервисов
discoverButton = new JButton( "Discover Lookup Services'
discoverButton.addActionListenar(
new ActionListener() {
// обнаружение сервисов поиска на заданной хосте
public void actionPerforsed( ActionEvent event )
(
discoverLookupServices();
}
Container contentPane = getContentPane(),-
contentPane.add( outputArea, BorderLayout.CBNTEK );
contentPane.add( discoverButton, BorderLayout.NORTH
Jini
93
51
52 ) // конец конструктора UnicaatDiscovery
53
54 // обнаружение сервисов поиска на веданном хоста и получение
55 // информации о каждом сернисе поиска вл объекта ServiceRegiatrar
56 public void discoverLookupServices()
57 {
58 // формирование URL Jini
59 String lookupURL = "jini://" + hostname + "/";
60
61 // соединение с сервисом поиска по адресу lookupURL
62 try (
63 LookupLocator locator = new LookupLocator f lookupURL );
64 outputAraa.append< "Connecting to " + lookupURL + "\n" );
65 // обнаружение с однонаправленный вещанием для получения
66 // объекта ServiceRegiatrar
67 ServiceRegiatrar registrar =
68 locator.getRegistrar();
69
70 // вывод информации о сервиса поиска
71 outputAraa.append( "Got ServieeRegistrar\n" +
72 " Lookup Service Host: " + locator.gatHoatf) + "\n" +
73 Lookup Service Port: " + locator.getPort() + "\n" );
74
75 // получение групп, которка поддерживает сервис поиска
76 String[] group* = registrar.getGroupa();
77 outputAraa.append( "Lookup service supports " +
78 + groups. length + ■' group (a) : \n" ) ;
79
80 // получение имен групп; если пусто, задать тип public
81 for ( int i = О; i < groups.length ; i++ ) (
82
83 if ( groups[ i ].equals( "" ) )
84 outputArea.append( " public\n" J;
85
86 else
87 outputArea.append( " " + groups[ i ] + "\n" );
88 }
89 }
90
91 // обработка исключения при неверной url
92 catch ( MalformedURLExcaption exception ) {
93 exception.printstackTrace();
94 outputArea.appand( exception.getMeasageO );
95 }
96 // обработка исключения при взаимодействии с объектом
97 // ServiceRegiatrar
98 catch { RemoteException exception ) (
99 exception.printStackTraca(};
100 outputArea.append( exception.getMeasage() );
101- \
102 // обработка исключения ClassNotFoundException при получении
103 // объекта ServiceRegistrar
104 catch ( ClasaNotFoundException exception ) {
105 exception.printStackTraca();
94 Глава 3
106 outputArea.append( exception.getMessage() );
107 }
108 // обработав исключения I OEat caption при получении
109 // объекта Serviceltegistrar
110 catch ( XOBxoeption exception ) {
111 exception.printstackTrace();
112 outputArea.append( exception.getHesaage() );
113 1
114
IIS ) // конец метода diacoverLookupServices
116
117 // аапуск приложении UnicastDiscovery
118 public static void main( String arg«[] )
119 (
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
13»
140 )
"1 )
Рис. 3.10. Класс UnicastDiscovery выполняет обнаружение с однонаправленным
вещанием для нахождения сервисов поиска Jim
Метод discoverLooknpServices (строки 56—115) обнаруживает сервисы на
конкретном компьютере. В строке 59 создается строка (String), представляющая URL
для компьютера, выполняющего сервис поиска. В этом URL должны быть указаны
протокол jini, имя хоста и, необязательно, порт для соединения (например,
jini://mycomputer.mydomain.com: 1234). Если порт в URL не указан, используется
порт по умолчанию 4160. Класс UnicastDiscovery прочитывает имя хоста из
параметра командной строки и сохраняет его в переменной экземпляра hostname.
В строке 63 создается новый объект LookupLocator для обнаружения сервисов
поиска. Этот конструктор LookupLocator принимает в качестве параметра URL Jini
для компьютера, выполняющего сервис поиска. Исключение MalformedURLEx-
ception возбуждается, если заданный URL не соответствует установленному
формату.
// задание ненедкера безопасности SacurityHanager
if ( System.gatSecurityHanager() ■» null )
System. aetSecurityManager(
new RMISecurityM&nager(J );
// проверка наличия хоста в командной строке
if ( arga.length !■ 1 ) (
Systern.err.printin(
"Osage: Java UnicastDiscovery hostname" );
)
// соадаииа объекта UnicastDiscovery для заданного имени хоста
•lea (
UnicastDiscovery discovery ~
new UnicastDiscovery( args[ 0 ] );
discovery. setDefaultCloseOpecation ( EXIT_ON_CLOSE ) ,-
discovery.pack(};
discovery.setVisible( true );
lini
95
В строке 68 осуществляется вызов метода getRegistrar класса LookupLocator
для выполнения обнаружения с однонаправленным вещанием. Метод get Registrar
возвращает объект ServiceRegistrar, который представляет сервис поиска.
Перегруженная версия метода getRegistrar принимает в качестве целочисленного
параметра максимальное значение времени ожидания (в миллисекундах}
обнаружения с однонаправленным вещанием при нахождении объекта ServiceRegistrar,
прежде чем выдать тайм-аут.
В строках 71-88 отображается информация об обнаруженном поисковом
сервисе в текстовой области oatpatArea. В строках 72-73 вызываются методы get Host
и get Port класса LookupLocator для извлечения имени хоста и номера порта, где
был обнаружен сервис поиска. В строке 76 вызывается метод getGroup интерфейса
ServiceRegistrar для извлечения массива поддерживаемых имен групп. Пустая
строка соответствует группе public.
Метод main (строки 118-140) запускает приложение UnlcastDiscovery. В
строках 121-122 задается менеджер безопасности, чтобы дать возможность загрузки
класса по сети. Когда класс UnlcastDiscovery обнаруживает новый сервис поиска,
классы, которые реализуют этот сервис поиска, загружаются по сети. Такая
загрузка класса создает угрозу безопасности, поскольку код будет исполняться
локально. Злоумышленник может осуществить несанкционированный доступ,
модифицировать или уничтожить важные данные на локальной машине. Установка
менеджера безопасности Security Manager позволяет не допустить выполнения
задач, которые явно не разрешены текущей политикой безопасности для
загруженного по сети кода. Для примеров в этой главе мы используем файл политики,
представленный на рис. 8.11, который предоставляет все разрешения (АНРег-
mission) коду в примерах. Это создает угрозу для безопасности и не должно
использоваться в рабочих приложениях.
В строках 126-128 в листинге на рис. 3.10 осуществляется проверка наличие
имени хоста в параметрах командной строки и вывод инструкции, если имя хоста
не предоставлено. В строках 133-138 создается новый экземпляр класса
UnlcastDiscovery и отображается пользовательский интерфейс.
Перед тем, как выполнить класс Uni cas tDisco very, запустите г mid, Web-сервер
и сервис поиска Reggie (при необходимости обратитесь к разделу 3.4). Чтобы
выполнить приложение U nicest Disco very, введите
Java —Djava.security.policy"политика
com.deital.advjhtpl.jini.discovery.UnicaetDiscovery хост
где политика — файл, который задает политику безопасности, а хост — имя
хоста или IP-адрес компьютера, выполняющего сервис поиска. Эту команду
необходимо выполнить в каталоге, который содержит пакет com.deitel.advjhtpl.
Переменная окружения CLASS PATH должна включать текущий катвлог. На рис. 8.12
показано, как выполняется приложение.
1 // policy.all
2 // предоставляет жсе разрешения (AllPermission) для кода (ОПАСНО!)
3 grant (
4 permission Java.security.AllPennission "", "";
5 }; .
Рис. 3.11. Файл политики, который предоставляет все разрешения AHPermission коду
Connoting to JrnMXXXXr
Del SenfceRegistrat
Lookup Bent с в HostXXXX
Lookup Service Port 418D
Lookup service supports 1 uroupfs).
Рис. 3.12. Результат выполнения приложения UnlcastDiscovery
3.6.2. Обнаружение с групповым вещанием
Обнаружение с групповым вещанием, или групповое обнаружение, дает
возможность сервису Jini или клиенту обнаруживать сервисы поиска, если
конкретный хост, выполняющий сервис поиска, неизвестен. Вспомним, что при
обнаружении с однонаправленным вещанием сервис Jini кли клиент должны запрашивать
сервисы поиска у конкретного хоста. Запрос на обнаружение с групповым
вещанием использует групповое вещание для обнаружения ближайших сервисов поиска.
Сервисы поиске, в свою очередь, периодически выдают групповые оповещения для
уведомления заинтересованных сервисов Jini и клиентов об имеющихся сервисах
поиска.
Приложение, представленное на рис. 3.13, демонстрирует групповое
обнаружение. Клаос Multicast Discovery использует класс net.jtni.distxvery.LookupDisco-
very для выполнения обнаружения. Класс MniticastDiscovery реализует
интерфейс DiscoveryListener (строка 22), чтобы дать возможность классу Muiticast-
Dlscovery принимать события DiscoveryEvent — уведомления об обнаруженных
сервисах поиска. В строке 36 создается объект LooknpDiscovery. Конструктор
LooknpDiscovery принимает в качестве параметра массив строк, в котором
каждый элемент представляет собой имя группы. Объект LooknpDiscovery будет
обнаруживать все ближайшие сервисы поиска, которые поддерживают группы,
заданные в этом строковом массиве. В строке 37 создается новый строковый массив
с пустой строкой в качестве единственного элемента. Тем самым указывается, что
экземпляр класса LooknpDiscovery должен обнаруживать сервисы поиска,
которые поддерживают группу public. В строке 43 вызывается метод addDiscovery-
Listener класса LooknpDiscovery для регистрации объекта MulticastDiscovery
в качестве слушателя для событий Discovery Event.
1 // MulticastDiscovery,Java
2 // MulticastDiscovery - приложение, демонстрирую»** обнаружение
3 // сервиса поиска Jini с понощыо группового вещаямяг.
4 package com.deitel.advjhtpl.jini.discovery;
5
6 // Набор бааовюс пакетов Java
7 import java.rmi.*;
8 import java.io.*;
9 import jav&.awt.*;
10 import java.atrt.event.*;
11
12 // Пакеты расширений Java
13 import javax.suing.*;
15 // Набор базовых пакете* Jini
16 import net.jini.сохе.lookup.ServiceRegistrar;
17
IB // Пакеты расширений Jini
19 import net.jini.discovery.*;
20
21 public class MulticastDiscovery extends JFrame
22 implements DiscoveryListener (
23
24 // число сервисов помеха, обнаруженных при групповом вешании
25 private int service&Found =0;
26
27 private JTextArea outputArea » new JTextArea( 10, 20 );
28
29 // конструктор MulticastDiscovery
30 public MulticastDiscovery ()
31 {
32 super( "MulticestDiscovery" ) ;
33 // обнаружение сервисов поиска в общей группе с помощью
34 // группового вещания
35 try (
36 LookupDiscovery lookupDisoovery = new LookupDiscovery(
37 new string[] ( ,-'- J );
38
39 outputArea.append( "Finding lookup services for " +
40 "public group . . An" ) ;
41
42 // прослушивание событий DiscoveryEvent
43 lookupDiscovery.addDiscoveryListener ( this );
44 }
catch ( lOException exception )
exception.printStackTrace();
getContentPane().add{ new JScrollPane( outputArea ),
BorderLayout.CENTER );
// получение уведомления о найденных сервисах
public void discovered ( DiscoveryEvent event )
{
// получение регистраторов ServiceRegistears дия найденных
// сервисов поиска
ServiceRegistrar[] registrars = event.getRegistrars();
int order « 0;
// получение информации о каждом найденной сервисе
for ( int i = 0; i < registrars.length ; i++ ) {
ServiceBegistrar registrar = registrars! i ] •'
if ( registrar != null ) (
68 // добавление нифорвации о каждом обнаруженном
69 // сервисе в текстовую область outputArea
70 try (
71 order = servicesFound + i + 1;
72
73 // получение имени хоста м номера порта
74 Runnable appender = пен TextAppender(
75 "Lookup Service " + order + ":\nh +
76 •' Host: " +
77 registrar.getLocator().getHost() + "\n" +
78 "\n Port: " +
79 registrar.getLoeafcor().getPort() + "\n" +
80 " Group support: " J;
81 // добавление к outputArea потока диспетчеризации
82 // событий
83 SvingOfcilities.invokeLater( appender };
84 // получение группы (групп), обслуживаемой сервисом
85 // поиска
86 String[] group» = registrar.getGroups();
87
88 StrlngBuffer names = new StringBuffer();
89
90 // получение имен групп, если пусто, задать public
91 for ( int j = 0; j < groups.length ; j++ ) (
92
93 if ( groups[ j J.equals( "" J }
94 names.append( "public\t" };
95 else
96 names.append( groups [ j ] + "\t" ) ,-
97 }
98 // добавление имей групп в текстовую область
99 // outputArea
100 SwingOtilities.invokeLater(
101 new TextAppender( names + "\n" ) ) •
102 )
103 // обработка исключения при ввениодействии с объектом
104 // ServiceRegistrar
105 catch ( RemoteException exception } (
106 exception.printStackTrace();
107 j
108 }
109 )
110
111
112
113
114 ) // конец метода discovered
115
116 // получение уведомлений об отвергнут»» сервисах поиска,
117 // которые больше не нужны
118 public void discarded! DiscoveryEvent event )
119 {
120 ServiceRegistrar(] discardedRegistrars =
121 event.getRegistrars();
122
123 SwingUtiliti.ee. invokeLater (
124 new TextAppender( "Number of discarded registrar»: +
125 discardedRegistrara.length + "\n" ) );
126 )
127
126 // TextAppender - класс типа Ruimsble для добавления техста в
129 // область outputArea с поыоцьж» потока диспетчеризации событий.
130 private claas TextAppender implement» Runnable (
131
132 private String textToAppend; // текст, добавляет* в outputArea
133
134 // кояструкгор TextAppender
135 public TextAppender( String text )
136 (
137 textToAppend « text;
138 )
139
140 // добавление текста в outputArea и прокрутка вниз
141 public void run()
142 f
143 outputArea.append( textToAppend ) ,-
144 outputArea.aetCaretPosition(
145 outputArea.getText().length() );
146 )
147
148 } // конец внутреннего класса TextAppender
149
150 // аапуск приложения MulticaetDiacovery
151 public static void main( String args[] )
152 (
153 // задание менеджера SecurityManager
154 if ( System.gatSecurityManager() = null )
155 System.setSecurityManager(
156 new RMISecurityManager 0 ) '>
157
158 MulticaetDiecovery discovery = new MulticastDiscovery();
159 discovery.aatDefaultClо■«Operation( EXIT_OH_CLOSE );
160 discovery.pack();
161 discovery.satvisible( true );
162 )
163 ) _ ___
Рис. 3.13. Класс Multicast Discovery выполняет обнаружение с групповым вещанием для
нахождения сервисов поиска Jmi
Класс LookupDiecovery вызывает метод discovered (строки 56-114), когда
объект LooknpDiscovery обнаруживает новые сервисы поиска. В строке 59
вызывается метод getRegistrar класса DfscoveryEvent для получения массива
обнаруженных регистраторов ServiceRegtetrar. В строках 74-80 создается новый объект
TextAppender, который содержит информацию о регистраторе ServiceRegUtrar,
и формируют строку вывода. В строке 83 вызывается метод invokeLater класса
SwingUtilities с объектом TextAppender в качестве параметра для добавления
текста в область ODtpntArea. В строках 91-97 осуществляется получение
информации о группе от регистратора ServiceRegietrar, а в строках 100-101 добавляется
информация о группе в область outputArea.
100
Глава 3
Класс LookapDiecovery вызывает метод discarded (строки 118-126), когда сервис
поиска должен быть отвергнут, если он больше не нужен, или если он больше не
соответствует набору групп, в которых заинтересован сервис Jini или клиент. В
строках 120-121 вызывается метод getRegistrar класса DiscoveryEvent для получения
массива освобожденных регистраторов ServiceBegistrar. В строках 123-125
добавляется количество отвергнутых регистраторов ServiceBegistrar для отображения.
Перед тем как выполнить этот пример, запустите несколько экземпляров
сервиса поиска Reggie. Если выполняется только один сервис поиска, результат будет
таким же, как и для класса Uni cast Discovery (пример на рис. 3.10). Чтобы
запустить несколько экземпляров сервиса поиска Reggie, укажите другой каталог
журнала па вкладке Reggie, затем щелкните на Start Reggie в панели Ron.
Попытайтесь добавить новые имена групп в один из сервисов поиска (например, "test" или
"MyGroop"). Чтобы выполнить класс MoIticastDiscovery, введите следующую
команду в командной строке:
Java -Djava.security,ро1±су=политика
Com.deitel.advjhtpl.jini.discovery. Mult±c*stDiscovery
где политика — это соответствующая политика безопасности. На рис. 3.14
показано окно с результатом выполнения программы, где представлено несколько
выполняющихся сервисов поиска.
.ookup Bemlce 1
Host ХХХЛ
Рол 14*8
G га up s и pport public test
Hostxxxx
Port 1485
Croup support public MyOroup
HOStXXXX
POIt 1431
Group support риОИс
jjokup Service 4
Н0,Я.ХШ1
Рис. 3.14. Результат выполнения приложения Mult least Disco very
3.7. Реализации сервиса и клиента Jini
В этом разделе мы разработаем сервис Jini, который предоставляет
информацию о фиктивных семинарах, проводимых компанией Deitel & Associates, Inc.
Затем мы создадим клиент для этого сервиса. Сервис Jini состоит из нескольких
компонентов, каждый из которых вносит вклад в гибкость и переносимость
архитектуры Jini. Посредник сервиса представляет собой промежуточное звено между
сервисом Jini и его клиентами. Посредник сервиса для семинара реализует
открытый интерфейс сервиса, который объявляет методы, предоставляющие сервис.
Посредник сервиса взаимодействует с реальной реализацией сервиса через так
называемый внутренний интерфейс (back-end) сервиса, который определяет методы
Jini 101
в реализации сервиса. Отдельное приложение обнаруживает сервисы поиска и
регистрирует сервис Jini, делая его доступным для клиентов Jini.
S Общая методическая рекомендация 3.1
Предоставлять внутренний интерфейс для реализации сервиса
необязательно. Однако использование внутреннего интерфейса делает сервис
Jini более гибким, поскольку реализация службы может быть изменена
без необходимости внесения изменений в посредник сервиса.
S Общая методическая рекомендация 3.2
Альтернативой предоставлению внутреннего интерфейса и его
реализации является реализация функциональных возможностей сервиса в самом
посреднике сервиса.
Клиент Jini использует представленные ранее в этой главе способы для
обнаружения сервисов поиска. Затем клиент использует обнаруженный сервис поиска
для нахождения желаемого сервиса Jini. Когда сервис поиска находит сервис,
запрошенный клиентом Jini, сервис поиска осуществляет сериализацию посредника
сервиса и доставляет посредник клиенту Jini. Клиент может после этого вызывать
методы, определенные в открытом интерфейсе сервиса, непосредственно через
посредник сервиса, который реализует этот интерфейс. Посредник сервиса
взаимодействует с реализацией сервиса через внутренний интерфейс.
Наш сервис Jini предоставляет информацию о фиктивных семинарах,
проводимых компанией Deitei & Associates, Inc. Информация об этих семинарах хранится
в экземплярах класса Seminar (рис. 3.15). Интерфейс Seminarlnterface (рис. 3.16)
является открытым интерфейсом для сервиса Jini. Класс SeminarProxy реализует
интерфейс Seminarlnterface и взаимодействует с реализацией сервиса через
интерфейс Backendlnterfaoe (рис. 3.17). Класс Seminar Info Service (рис. 3.21)
обнаруживает сервисы поиска и регистрирует сервис Seminarlnfo Jini. Класс U ni cast Seminar-
InfoClient (рис, 3.22) представляет собой клиент Jini, который использует
обнаружение с индивидуальным вещанием для об кар ужения сервисов поиска и нахождения
сервиса Seminarlnfo Jini. Этот клиент дает возможность пользователю выбирать день
недели и просматривать, какие семинары проводятся в этот день.
3.7.1. Интерфейсы сервиса и классы поддержки
Класс Seminar (рис. 3.15) представляет фиктивный семинар, включая название
семинара и место его проведения. Он реализует интерфейс Serializable, поэтому
объекты этого класса могут 5ыть отправлены сервисом Jini его клиентам через сеть.
В строке 13 явно задается идентификатор BerialVersionUm для класса
Seminar. Разработчики могут определить этот статический член в классах Serializable,
чтобы обеспечить совместимость между версиями таких классов. Бели объект
одной версии класса является сериалнзованным, объект может быть десериализован
в объект новой версии класса, поскольку обе версии используют один и тот же
идентификатор serialVeraionUID (и реализованы совместимым образом).
Изменение идентификатора serialVeraionUID для новой версии класса указывает, что
новая версия не является совместимой с предыдущими версиями. В атом случае десе-
риализация не будет корректно работать.
Интерфейс Seminarlnterface (рис. 3.16) определяет единственный метод get-
Seminar, который принимает в качестве строкового параметра день недели. Метод
get Seminar возвращает объект Seminar, содержащий информацию о семинаре,
проводимом в этот день. Посредник сервиса должен реализовывать этот
интерфейс, поскольку клиенты Jini используют этот интерфейс для взаимодействия
с сервисом.
102
Глава 3
1 // Seminar.Java
2 // Класс Seminar представляет семинар или лекцию, включая
3 // название семинара и место его проведения.
4 package com.deitel.advjhtpl.jini.seminar;
5
6 // Набор базовых пакетов Java
7 import java.io.Serializable;
8
9 public class Seminar implements Serializable
10 i
11 private String title;
12 private String location;
13 private static final long serialVersionOID = 20010724L;
14
15 // конструктор Seminar
16 public Seminar{ String seminarTitle, String eeminarLocation 1
17 I
18 title = seminarTitle;
19 location = seminarLocation;
20 }
21
22 // получение строки, представляйте* объект Seminar
23 public String toStringf}
24 {
25 return "Seminar title: " + getTitle() +
26 "; location: " + getLocation();
27 }
28
29 // получение названия семинара
30 public String getTitle()
31 (
32 return title;
33 }
34
35 // получение места проведения семинара
36 public String getLocation(}
37 {
38 return location;
39 }
ДО }
Рис. 3.15. Класс Seminar содержит место проведения и название семинара
1 // Seminarlnterfасе.Java
2 // Интерфейс SeminarInterface определяет методы.
3 // доступные ив сервиса Seminarlnfo Jini.
4 package com.deitel.advjhtpl.j ini.seminar.service;
5
6 // Набор базовых пакетов Java
7 import java.rmi.Remote;
8
9 // Пакеты Deitel
10 import com.deitel.advjhtpl.jini.seminar.Seminar;
11
Jlni
103
12 public interface SeminarIntecface {
13
14 // получение объекта Seminar для заданной дат
15 public Seminar get5eminar( String date };
16 }
Рис. 3.16. Интерфейс Semlnarlnterface определяет методы, доступные из сервиса
Seminarlnfo Jini
Интерфейс Backendlnterface (рис. 3.17) определяет методы, которые
посредник сервиса использует для взаимодействия с реализацией сервиса. Посредник
вызывает метод getSeminar для извлечения информации о семинаре для
запрашиваемого дна. В этом сервисе Jini посредник семинара взаимодействует с
реализацией сервиса с помощью RMI, хотя реализации Jini могут использовать для
взаимодействия с внутренними реализациями любой протокол.
® Общая методическая рекомендация 3.3
Jini не обязывает посредников сервисов взаимодействовать с
внутренними реализациями с помощью RMI. Посредники сервиса могут
использовать RMI, TCP/IP, СОЮЗА или любой другой подходящий протокол для
соединения с внутренними реализациями.
1 // Backendlnterface.Java
2 // Backendlnterface определяет интерфейс, через который
3 // посредник сервиса взаимодействуем с внутренним сервисом.
4 package сов.deital.advjhtpl.jini.seminar.service;
5
6 // Набор баэоаых пакетов Java
7 import Java.rati.*;
в
9// Пакеты Deitel
10 import com.deitel.advjhtpl.jini.seminar.Seminar;
11
12 public interface Backendlnterface extends Remote (
13
14 // получение объекта Seminar для заданного дня недели
15 public Seminar getSeminar( String day ) throws RemoteException;
16 }
Рис. 3.17. Интерфейс BadcEndlnterface определяет методы, доступные для посредника
сервиса Seminarlnfo
Ш Общая методическая рекомендация 3.4
Посредники сервисов могут использовать не-Java протоколы (например,
TCP/IP или CORBA) для взаимодействия с внутренними реализациями,
давая возможность программистам предоставлять сервисы Jini, которые
реализованы на языках, отличных от Java.
3.7.2. Посредник сервиса и реализации сервиса
Класс SeminarProxy (рис. 3.18) представляет собой посредник для сервиса
Seminarlnfo Jini. В строке 12 указывается, что класс SeminarProxy ревлизует
интерфейс Seininarlnterface, который является открытым интерфейсом для сервиса
Seminarlnfo Jini. Посредник сервиса должен ревлизовывать этот интерфейс,
чтобы дать возможность клиентам Jini взаимодействовать с сервисом Seminarlnfo.
Посредник сервиса также должен реализовывать интерфейс Serializable
(строка 13), чтобы эти экземпляры могли быть доставлены удаленному клиенту через
RMI. Конструктор SeminarProxy (строки 18-21) иницивлизирует посредник с
помощью уделенной ссылки на внутреннюю ревлизадию. Метод get Seminar
(строки 24—38) вызывает метод get Seminar внутренней реализации для извлечения
информации о семинаре (строка 28).
1 // SeminarProxy.java
2 // SeminarProxy - посредник для сервиса Seminarlnfo Jini.
3 package со».deitel.advjhtpl.jini.seminar.service;
4
5 // Набор базовых пакетов Java
6 import Java.io.Serializable;
7 import java.rmi.*;
В
9 // Пимш Oaitel
10 import com.deitel.advjhtpl.jini.seminar.Seminar;
11
12 public claim SeminarProxy implements Seminarlnterface,
13 Serializable (
14 '
15 private Backendl titer face back Interface ;
16
17 // конструктор SeminarProxy
18 public SeminarProxy( Backend!nterface inputlnterface }
19 {
20 backlnterface = inputlnterface;
21 }
22 // получение объекта Seminar для заданной даты через интерфейс
23 // Backendlnterface
24 public Seminar getSeminar( String date )
25 <
26 // получение объекта Seminar от сервиса через интерфейс
// Backendlntarface
27 t»j (
28 return backlnterface.getseminar( date );
29 }
30 // обработка исключения при взаимодействии
31 // с внутренний сервисом
32 catch ( RemoteException rentoteException } (
33 remoteException.printStackTrace();
34 }
35
36 return null;
37
38 } // котец метода getSemiпаг
39 }
Рис. 3.18. SeminarProxy - посредника сервиса, который клиенты используют для
взаимодействия с сервисом Seminarlnfo
Класс Seminarlnfo (рис. 3.19) представляет собой объект RMI, который
реализует внутренний интерфейс сервиса Jini. Посредник сервиса взаимодействует
с этой внутренней ревлизацией через интерфейс Backen dint erfасе с помощью
Jini
105
RMI. Метод get Seminar (строки 29-91) считывает информацию о семинаре из
файла SeminarInfo.txt и возвращает новый объект Seminar, содержащий
информацию о семинаре для заданного дня недели.
1 // Seminarlnfo.Java
2 // SeminarInfo - сервис Jini, который предоставляет
3 // информацию о семинарах, проводящихся в течение непени.
4 package com.deital.advjhtpl.jini.seminar.service;
5
6 // Набор Садовых пакетов Java
7 import java.io.*;
8 import Java.nni.server.OnicastRemoteObject;
9 import java.rnii.RemoteException;
10 import java.util.StringTokenizer;
11
12 // Пакеты Deitel
13 import com.deitel.advjhtpl.jini.seminar.Seminar;
14
15 public class SeminarInfo extends OnicastRemoteObject
16 implements Backendlnterface (
17
18 // строки, представляжвдге дни нелени
19 private static final String MONDAY = "MONDAY";
20 private static final String TUESDAY = "TUESDAY";
21 private static final String WEDHESDAY = "WEDNESDAY";
22 private static final String THURSDAY = "THURSDAY";
23 private static final String FRIDAY = "FRIDAY";
24
25 // конструктор бая параметров Seminarlnfo
26 public SeminarInfо() throws RemoteException (}
27
28 // получение информации о семинара для заданного дня
29 public Seminar getSeminar{ String date }
30 throws RemoteException
31 (
32 StringU titles = new String[] ( ■■", ■'", "", "", ■'" };
33 Stringl) locations = new StringU i ""- ""- "". "", "" 1.'
34
35 // чтение информации о семинаре мэ текстового файла
36 try (
37 String filename = Seminarlnfo.class.getResource{
38 "SeminarInfo.txt" ). toStringO;
39 fileName = filename.substring( 6 );
40
41 FileInputStream inputStreaa =
42 new FilelnputStream( fileName };
43
44 BufferedReader reader = new BufferedReader(
45 new ZnputStxeamReader( inputStream }};
46
47 String line = reader.readbine{);
48
49 // чтение информации о семинаре из файла
50 for ( int lineNo =0; ( line != null )
51 it ( lineNo < 5 }; lineHo-t-t- ) {
52 StringTokenizer tokenizer =
106
Глава 3
53 пем StringTokenizer( line, ";" };
54
55 title*[ linaHo ] = token!zar.nextToken0•
56 locations[ lineHo ] = tokenizer.nextTokenO ;
57 line = reader. readLineO;
58 }
5Э )
60
61 // обработка исключения при аагруаке файла семинара
62 catch I FileNotFoundException fileException } 1
63 fileException.printStackTraeeO;
64 }
65
66 // обработка исключения при чтении иа файла семинара
61 catch { IOException ioException ) (
68 ioException.printStackTraeeO;
69 )
70
71 // сопостажлекхе еадакного дня недели с доступными семинарами
72 If ( data.equalsignoreCaael MONDAY ) ) (
73 return пем Seminar( titles[ 0 ], locations[ 0 ] } ;
74 )
75 else if ( date.equalaIgnoreCaee( TUESDAY } ) (
76 return new Seminar( titles[ 1 ], locations[ 1 ) };
77 }
76 else if ( data.equalsignoreCaее( WEDNESDAY ) } (
79 return new Seminar ( titles [ 2 ], locations [ 2 ] ) ,-
BO }
SI else if ( data.equalsIgnoreCase( THURSDAY } } (
82 return пем Seminar{ titles[31, locations[ 3 ] };
83 }
84 else if ( date.equalslgnoreCase( FRIDAY } } (
85 return пем Seminar( titles[ 4 ], locations[ 4 ] };
86 >
87 else 1
88 return пем Seminar( "Empty", "Hot available" };
89 }
90
91 } // конец метода getSeminar
92 }
Рис. 3.19. Класс Seminarlnfo реализует сервис Seminaiinfo Jini
Файл Seminarlnfo.txt (рис. 3.20) содержит информацию о семинаре, которую
сервис Seminarlnfo предоставляет своим клиентам. Название семинара отделено
от места проведения семинара точкой с запятой.
1 Advanced Swing GDI Components; Deitel Seminar Room
2 Model-View-Controller Architecture; Deitel Seminar Room
3 Java 2 Enterprise Edition; Deitel Seminar Room
4 Introduction to Jini,- Deitel Seminar Room
5 Java 2 Micro Edition; Deitel Seminar Room
Рис. 3.20. Содержимое файла Seminarlnfo.txt
3.7.3. Регистрация сервиса сервисом поиска
Класс Seminar Service (рис. 3.21) обнаруживает сервисы поиска с помощью
группового обнаружения и регистрирует сервис Seminar Info с помощью
обнаруженных сервисов поиска. В строках 29-30 создается объект LookupDiscovery для
выполнения обнаружения с помощью группового вещания для группы public.
В строке 33 осуществляется регистрация объекта SeminarlnfoService как
слушателя Disco veryListcner для получения уведомлений об обнаружении сервисов
поиска. В строке 42 создается массив объектов содержимого Entry. Объект класса
Entry (из пакета net. jini. core .entry) описывает сервис, который дает возможность
клирнтям Jini находить сервисы с определенным описанием. В строке 43 создается
новое имя (объект класса Name) объекта содержимого Entry (из пакета net.ji-
ni.lookup.entry) и добавляется в массив entries, чтобы предоставить имя сервиса
Jini. В строках 46-47 создается новый элемент сервиса (объект класса Serviceltem
из пакета net. jini .core.lookup) для сервиса Seminarlnfo Jini. Объект Serviceltem
нужен сервису поиска, чтобы зарегистрировать сервис Jini. Первым параметром
конструктора Serviceltem является идентификатор сервиса Jini. Параметр null
в строке 53 предписывает сервису поиска присвоить сервису новый, уникальный
идентификатор. Чтобы обеспечить постоянство действия сервиса, поставщик
сервиса должен использовать ранее назначенный идентификатор сервиса при
перерегистрации сервиса. Вторым параметром является экземпляр посредника сервиса
для сервиса Jini. Третьим параметром является массив объектов содержимого
Entry, которые описывают сервис.
1 // SeminarInfoService.Java
2 // SeminarlnfoService обнаруживает сервис» поиска и
3 // регистрируем сервис Seminarlnfo этими сервисами поиска.
4 package com.deital.advjhtpl.jini.seninar.service;
5
€ // Набор базовых пакетов Java
7 import Java.rmi.RMISecurityManager;
8 import java. rmi .RemotaExceptioii;
9 import java.io.IOException;
10
11 // Набор базовых пакетов Jini
12 import net.jini.core.lookup.*;
13 import net.jini.core.entry.Entry;
14
15 // Пакеты расширений Jini
16 import net.jini.discovery.*;
17 import net.jini.lookup.entry.Name;
IS
19 public class SeminarlnfoService implements DieaoveryListener {
20
21 private Serviceltem serviceltem;
22 private final int LEASETIME = 10 * 60 * 1000;
23
24 // конструктор SeminarlnfoService
25 public SeminarlnfoService()
26 (
27 // обнаружение сервисов поиска для группы public
28 try {
29 LookupDiscovery discover =
30 new LookupDiscovery( new String[] ( "" } );
// добавление слушателей для событий DiscoveryEvents
discover.addDiscoveryListener( this };
// обработка исключения при соядаиин об<ьекта LookupDiscovery
catch ( IOSxception exception } (
exception.printStackTrace(>;
}
// соадокне массива элементов Entry для этого сервиса
Entry[] entries = new Entry[ 1 J;
entries[ 0 ] = new-Name( "Seminar" };
// установка посредник* сервис* к имени массива
// элементов Entry
serviceItem = new Serviceltem(
null, createProxy£), entries };
} // конец конструктора SeminarlnfoService
// получение уведомлений об обнаружении сарвиоов поиска
public void discovered( Diecoverytvent «vent }
I
ServiceRegistrar[] registrars = event.getRegistrars();
// регистрация сервиса каждым ма еервиоот поиска
for ( int i = 0; i < registrars.length; i++ ) {
ServiceRegistrar registrar » registrars[ i ];
// рагистрация сервиса обнаруженным сервисом поиска
try (
ServiceRegistration registration =
registrar.register( serviceItem, LEASETIHE };
}
// перехват удаленного исключения
catch '( RemoteException exception) (
exception.printStackTrace() ;
}
} // конец блока for
} // конец метода discovered
// игнорирование отвергнутых сервисов поиска
public void discarded( DiscoveryEvent event } (}
// создание посредника для сервиса семинара
private SeminarInterface createProxy()
(
// повучениа интерфейса Backendlnterface для сервиса
//и соадаиме объекта SeminarProxy
try (
return new SeminarProxy( new SeminarInfо(} );
86 // обработка исключения ори создании объекта SeminarProxy
87 catch { EeraoteException exception ) {
88 exception.printStackTracef) ;
89 )
90
91 return null;
92
93 ) // конец нехода discovered
94
95 // метод main поддерживает активное состояние прилотания
96 public static void main( String args[] )
91 (
98 // установка менеджера SecurityManager
99 if ( Syaten.getSecurityHanagerO = null )
100 System.aetSecurityManager( new RMISecurityManager() );
101
102 new SeminarInfoService();
103
104 Object keepAlive = new QbjectO;
105
106 synchronized ( keepAlive ) {
107
108 // поддержание активного состояния приложения
109 try t
110 keepAlive.wait();
111 )
112
113 // обработка исключения, если ожидание было прервано
114 catch ( InterruptedException exception ) {
115 exception.printStaekTrace();
116 )
117 >
118
119 ) // конец метода main
120 ) ________^^
Рис. 3.21. Класс SeminarlnfoServke регистрирует сервис Seminarlnfo с помощью
сервисов поиска
Метод discovered (строки 52-73) принимает уведомления об обнаруженных
сервисах поиска. Для каждого обнаруженного сервиса поиска в строке 62 вызывается
метод register интерфейса Service Regis tar для регистрации сервиса serviceltem
Jini сервисом поиска. Метод register принимает в качестве параметров объект
serviceltem, который содержит посредник сервиса и время аренды. Время аренды
задает период времени, в течение которого сервис будет доступен через сервис по-
иска. Для данного примера время аренды устанавливается 10 минут. Значение
LEASETIME, задаваемое в методе register, представляет собой запрашиваемое
сервисом время аренды. В действительности реализация Sun ограничивает это
время 5 минутами. Это означает, что желаемое время аренды не обязательно будет
предоставлено. После истечения 10 минут, или даже меньше, время аренды
сервиса Jini завершается, и сервис больше не будет доступен через сервис поиска.
Поставщик сервиса ответственен за сохранение объекта ServiceRe gist rat ion
(строка 62), возвращаемого методом register, и его использование для периодического
возобновления аренды. Подробнее об аренде и управлении арендой мы поговорим
в разделе 3.8.3.
Глава 3
Метод createProxy (строки 79-93) создает новый посредник сервиса Seminar-
Proxy для нашего сервиса Jini. В строке 83 создается новый экземпляр внутренней
реализации Seminar Info. В строке 84 возвращается новый посредник сервиса
SeminarProxy для внутренней реализации сервиса Seminarlnfo.
Метод main (строки 95-118) запускает приложение SeminarlnfoService. В строке
99 устанавливается менеджер безопасности SecurityManager, а в строке 101
создается новый экземпляр класса SeminarlnfoService, который обнаруживает сервисы
поиска и регистрирует сервис Seminarlnfo. В строках 103-115 создается объект с
именем keepAlive. Блок synchronized (строки 105-116) предотвращает завершение
выполнения программного потока main, что привело бы к завершению приложения
SeminarlnfoService, и, тем самым, к отключению сервиса Seminarlnfo. Если это
случится, клиенты не смогут осуществлять доступ к информации о семинарах.
3.7.4. Клиент сервиса Jini
Класс UnicastSeminarlnfoClient (рис. 3.22) представляет собой клиент Jini,
который использует сервис Seminarlnfo для извлечения информации о семинарах
для заданного дня. Класс UnicastSeminarlnfoClient выполняет обнаружение с
однонаправленным вещанием с помощью сервиса поиска. Конструктор
UnicastSeminarlnfoClient (строки 44-82) инициализирует графический
пользовательский интерфейс приложения. В строке 51 создается кнопка J Batten, на которой
пользователь может щелкнуть, чтобы начать процесс обнаружения и извлечения
информации о семинаре. В строке 61 вызывается метод discoverLookupServices
для обнаружения сервисов поиска с использованием индивидуального вещания.
В строках 63-64 осуществляется получение удаленной ссылки на посредник
сервиса Seminarlnfo с помощью вызова метода looknpSeminarService. В строках
66-69 у пользователя запрашивается день недели, для которого он хотел бы
получить информацию о семинаре. В строке 71 вызывается метод shnwSeminars для
отображения информации о семинаре для заданного дня.
1 // OnicaatSeminarlnfoClient.java
2 // Приложение OnicaatSeminarlnfoClient нспольауат обнаружажиа
3 // с однонаправленным вещанием дня нахождения сервисов поиска для
// Seminarlnfo.
4 package coa.daitel.advjhtpl.jini.seminar.client;
5
6 // Набор базовых пакетов Java
7 impost java.awt.*;
8 import java.awt.event.*;
9 import java.io.*;
10 import java.rmi.*;
11 import java.net.*;
12 import java.util.*;
13
14 // Пакеты растираний Java
15 import javax.swing. *,-
1€
17 // Набор базовых пакетов Jini
18 import net.jini.core.discovery.LookupLocator;
19 import net.jini.core.lookup.*;
20 import net.jini.core.entry.Entry;
21
22 // Пакеты растирании Jini
23 import net.jini.lookup.entry.Наше;
25 // Пакет" Deitel
26 import com.deitel.advjhtpl.ji;
27 import com.deitel.advjbtpl.ji:
28
29 public class OnicastSeminarlnfoClient extends JFrame {
30
31 // строки, представляющие дни недели, в которые
32 // проводятся семинары
33 private static final String!3 days = ( "Monday", "Tuesday",
34 "Wednesday", "Thursday", "Friday" };
35
36 // pout хоста и регистратор ServiceRegistrar для сервисов поиска
37 private String hostname;
38 private ServiceRegistrar registrar;
39
40 // кнопка JButton для поиска семинаров
41 private JButton findSeminarButton;
42
43 // конструктор OnicastSeminarlnfoClient
44 public OnicastSeminarlnfoClient( String host )
45 {
46 super( "OnicastSeminarlnfoClient" );
47
48 hostname = host; // задание имени хоста для обнаружения
49
50 // создание кнопки JButton для поиска семинаров
51 £indSeminarButton = new JButton( "Find Seminar" );
52 fIndSeminarButton.addActionLiatener(
53
54 new ActionLietener(J {
55
56 // обнаружение сервисов поиска, поиск сервиса
57 // Semin&rlnfo, запрос у пользователя дня недели и
58 // отображение проводимых в этот день семинаров
59 public void actionPerformed( ActionEvent event )
60 {
61 discoverLookupServices();
62
63 SeminarIntarface seminarService =
64 1оokupSeminarService();
65
66 String day = ( String ) JOptionPane.showInputDialog(
67 OnicastSeminarlnfoClient.this, "Select Day",
68 "Day Selection", JOptionPane.QUESTION_MESSAGE,
69 null, dsys, days{ 0 ) );
70
71 shOMSeminars{ seminarService, day );
) ,' // конец обращения к addActionLiatener
jpanel huttonPanel = net* J?anel{);
buttonPanel.add( findSeminarButton );
112
Глава 3
вО getContentPana{).add{ buttonPanel, BorderLayout.CENTER );
81
82 ) // конец конструктора nnicastSeminarlnfoClient
вэ
84 // обнаружение сервисов) поиска с помощью однонаправленного вещания
85 private void discoverLookupServices0
86 (
37 String lookupURL = "jini://" + hostname + ■'/"<"
88
89 // получение указателя на сервисы поиска, размещенные на
90 // jini://hostname с использованием aopva по умолчанию
91 try 1
92 LookupLocator locator = new LookupLocator{ lookupURL );
93
94 // возврат регистратора
95 registrar = locator .getRegistrar() ;
9Б }
97
98 // обработка исключения при обнаружении сервисов поиска
99 eaten ( Exception exception ) {
100 exception.printstackTracet);
101 )
102
103 } // конец метода discoverLookupServiceS
104 // поиск сервиса SeminarInfo в указанном регистраторе
105 // ServiceRegistrar
106 private SeminarInterface lookupSeminarService ()
107 {
108 // задание требований к сервису
109 Class[] types = new Class[] { SeminarInterface.class };
110 Entry[} attribute = new Entry[] { new Name{ "Seminar" ) };
111 ServiceTemplate template =
112 new ServiceTempiata( null, types, attribute );
113
114 // поиск сервиса
115 try <
116 SeminarIntarface seminarInterface =
117 { SeminarInterface ) registrar.lookup{ template );
118 return seminarInterface;
119 }
120
121 // обработка исклвчежия при поиске сервиса Seminarlnfo
122 catch { RemoteExaeption exception ) {
123 exception.printStackTrace{) ;
124 >
125
126 return null;
127
128 ) // конец метода lookupSeminarServiее
129
130 // отображение семинара
131 // для заданного дни недели
132 private void snowSeminars( SeminarInterfаса seminarService,
133 String day )
134 t
135 StringBuffer buffer * new StringBuffer{);
136
137 // получение семинара ив обмята Seminarlnfo
138 if ( seminarService != null ) {
139 Seminar seminar ■ seminarService.getSeminar( day );
140
141 // получение :
142 buffer.append{ "Seminar information:
143 buffer.append( day + ":\n" );
144 buffer.append( seminar.getTitle() + "\n" ); // название
145 buffer.append( seminar.getLocation() ) ; // насто проведения
146 )
147 else // сернис Seminarlnfo недоступен
148 buffer.append{
149 "Seminarlnfo service does not available. \n" );
150
151 // отображение информации о семинаре
152 JOptionPane.shoMMessagaDialogt this, buffer );
153
154 ) // конец метода showSeminaxs
155
156 // запуск приложения UnicastSeminarlnf©Client
157 public static void Hain { String args{) )
158 (
159 // проверка наличия имени хоста в параннтрах командной строки
160 if ( args.length != 1 ) (
161 Systen.err.println(
162 "Usage: Java OnicastSerainarlnfoClient hostname" );
163 }
164
165
166 else {
167 System.setSecurityHanager{ new RHIS*curityHanager() );
166
169 OnicastSeminarlnfoClient client =
170 new pnicastSeminarlnfoClient( arge{ 0 ] );
171
172 client.aetDefaultCloseOperationt EXIT_ON_CLOSE );
173 client.pack();
174 client.aetSizet 250, 65 );
175 client.eetvisible( true );
176 }
177
178 } // конец метода Hain
179 )
Рис. 3.22. Класс UnkastSeminarlnfoClient предстваляет собой клиент для сервиса
Seminarlnfo
Метод disco verLooknpServices [строки 85-103) выполняет обнаружение с
однонаправленным вещанием для обнаружена сервисов поиска на определенном хосте,
задаваемом в командной строке. В строке 92 создается объект LooknpLocator, a
в строке 95 вызывается метод getRegistrar класса LooknpLocator для получения
ссылки ServiceRegistrar на сервис поиска, которая хранится в переменной
экземпляра registrar.
Глава 3
Метод looknpSeminarService использует ссылку ServiceRegistrar,
обнаруженную в методе discoverLookupService, для получения посредника SeminarProxy
для сервиса Seminarlnfo. В строке 109 создается массив объектов Class и
инициализируется первый элемент как объект типа Class интерфейса Seminal-Interface.
Объект ServiceRegistrar использует этот массив объектов Class для обнаружения
соответствующих посредников сервисов для клиента Jini. В строке 110 создается
массив объектов типа Entry. Вспомним, что когда мы регистрировали сервис
Seminarlnfo, мы предоставили массив объектов Entry для описания этого сервиса.
В строке 110 массив entries заполняется одним объектом тина Name. Объект
ServiceRegistrar использует этот массив объектов содержимого Entry для
нахождения желаемого сервиса. В строках 111-112 создается объект ServieeTemplate
[пакет net.jini.core.lookup), который содержит массивы Class и Entry из строк
109-110. ServiceRegistrar использует этот объект ServieeTemplate для
нахождения сервиса, который соответствует заданному набору объектов Class и Entry.
Первым параметром конструктора ServieeTemplate является идентификатор
сервиса Jini. Мы передаем параметр nail, указывая, что нам не известен
идентификатор сервиса.
f В строках 116-117 вызывается метод lookup интерфейса ServiceRegistrar для
выполнения поиска и извлечения посредника сервиса SeminarProxy. В строке 117
осуществляется передача объекта ServieeTemplate методу lookup. Регистратор
ServiceRegistrar сопоставляет информацию в объекте ServieeTemplate и Service-
Item, зарегистрированным в сервисе поиска. Чтобы объект Serviceltem
соответствовал шаблону ServieeTemplate, если ServieeTemplate содержит ненулевой
идентификатор сервиса, идентификатор сервиса должен совпадать с идентификатором
в шаблоне ServieeTemplate. Наконец, атрибуты сервиса должны совпадать с
одним или несколькими атрибутами в массиве Entry шаблона ServieeTemplate.
В строке 118 осуществляется передача ссылки на посредник SeniinarProxy,
возвращаемой методом lookup.
Метод showSeminars (строки 132-154) принимает н качестве параметров Semi-
narlnterface и строку, содержащую день недели, для которого пользователь хотел
бы получить информацию о семинаре. В строке 139 вызывается метод getSeminar
интерфейса Seminarlnterface и в качестве параметра передается день недели.
Вспомним, что интерфейс Seminarlnterface является открытым интерфейсом для
сервиса Seminarlnfo Jini и определяет все методы, доступные для этого сервиса.
В строке 152 отображается информация о семинаре в окне сообщения JOptionPane.
Метод main (строки 157-178) запускает приложение UnicastSeminarlnfo-
Client. В строках 160-163 проверяется ввод пользователем имени хоста, на
котором выполняется обнаружение сервиса поиска. В строках 167-175
устанавливается менеджер безопасности Security Manager и запускается приложение Unicast-
SeminarlnfoCUent.
Выполнение сервиса и клиента
Клиенты и сервисы обычно выполняются на разных компьютерах. Чтобы
сымитировать это на одном компьютере, мы должны разделить файлы классов для
клиента Jini, файлы классов для сервиса Jini и файлы классов сервиса Jini,
загруженные из сети. В этом примере мы упаковываем файлы классов сервиса в JAR-
файл с именем Seminar Service, jar. В таблице на рис. 3.23 представлено
содержимое архива SeminarServlce.jar.
Вспомним, что класс Setninarlnfo представляет собой удаленный объект RMI,
из которого посредник SeminarProxy извлекает информацию о семинаре. Это
требует, чтобы мы использовали компилятор RMI (rmic) для компиляции файла
заглушки для класса Seminarlnfo и поместили этот файл заглушки (Seminar-
Info_S tub. class) в архяа Seminar Service, jar.
Jini
115
Файл класса
Seminar.class
Seminarlnterface.class
SeminarProxy. class
Backendlnterface.class
Seminarlnfo.сlas s
Seminarlnfo Stub.class
Seminar Inf.oService.claea
Каталог а архиве SeminarServke.jar
com\deitel\adv jhtpl\j ini\saminax\
com\daitel\adV j h tpl\jini\aaminar\service\
cora\deitel\advjhtpl\jlni\semin»r\*ervice\
com\de i t«1\»dv jhtpl\j ini\aaminar\«arvice\
com\da1t«1\»dvjntpl\jini\seminar\service\
cca\deit«l\advjhtpl\3ini\aaminax\s*£vice\
com\deitel\«dvjhtpl\j ini\seminar\service\
Рис. 3.23. Содержимое архива SeminarServke.jar
Мы помещаем наши файлы классов клиента Jini в JAR-файл SeminarCIi-
ent.jar. Чтобы осуществить доступ к сераису Jini Seminarlnfo, клиенту необходим
файл класса для открытого интерфейса сервиса, а также классы поддержки,
которые используются методами открытого интерфейса. Архив SeminarClient.jar
содержит файлы Seminar.elass и Seminar In terface.class. Последний содержит
открытый интерфейс сервиса. В таблице иа рис. 3.24 представлено содержимое
архива SeminarClient.jar.
I Файл класса | Каталог в архиве SemlnarCH«nt.jar
Seminar.class
____ | сош\deltel\adrjhtpl\ jini\aeminar\
Seminarlnterface.class
| com\deitel\advjhtpl\jlpi\a«minar\nerTfioe\
UnicestSaminarlnfoClient.class
|com\deitel\adT3htpl\jini\aaminar\client\
UnicestSeminarInfoCli«nt$l.class
|com\daibel\advjbtpl\3iai\sewinar\client\
UnicestSeminaxInfoClient$2.class
'com\deitel\advjhtpl\jini\saainar\cliant\
Рис. 3.24. Содержимое архива SeminarClient.jar
Когда клиент запрашивает сервис Seminarlnfo у сервиса поиска, клиент
использует загрузку классов по сети для получения класса посредника сервиса и
выполнения методов, имеющихся в сервисе Jini. Следовательно, мы должны
упаковать необходимые файлы классов для загрузки их клиентом Jini. Клиент
запрашивает файл класса посредника сервиса и файлы классов для объектов, на которые
ссылается посредник сервиса. Вспомним, что клиент взаимодействует с
посредником сервиса через интерфейс Seminarlnterface. Клиенту ничего не известно о
посреднике сервиса Seminar Proxy или его классах поддержки. Следовательно,
клиент должен загрузить файл класса SeminarProxy н его файлы классов поддержки
на этапе выполнения с помощью сетевой загрузки классов. К загружаемым из сети
классам относятся Backendlnterface.class и Seminarinfo_Stnbxlass, которые
посредник сервиса использует для взаимодействия с удаленным объектом
Seminarlnfo. Мы упаковываем эти файлы в архив SeminarServiceDownloadJar (рис. 3.25)
и публикуем этот JAR-файл на Web-сервере для загрузки его клиентом.
116 Глава 3
Файл класса
SeminarProxy.class
Backendlnterf«с*.claaa
SemiпегInfo Stub.clsss
Каталог в архиве SeminarservkeDownloaAjar
com\deite1\advjhtpl\jini\semiпаг\service\
com\deital\advjhtpl\jini\eeminar\service\
com\daitel\advjhtplAjini\seminax\sezrvice\
Рис. 3.25. Содержимое архива SeminarServiceDownload.jar
После создания этих JAR-файлов запустите rmid, Web-сервер и сервис поиска
Reggie (если потребуется, обратитесь за информацией к разделу 3.4).
Сконфигурируйте и запустите дополнительный Web-сервер, чтобы дать возможность клиентам
загружать посредник сервиса и файлы поддержки. Поместите архив
SeminarServiceDownload.jar в каталог Document Area, указанный при настройке этого
дополнительного Web-сервера (например, C:\Jini\seminar\service). На рис. 3.26
представлена настройка Web-сервера, использующего порт 9090. Мы задаем этот номер
порта в свойстве Java.rml.server.codebase командной строки при запуске сервиса
Seminarlnfo.
Рис. 3.26. Конфигурация Web-сервера для сервиса Seminarlnfo
Запустите сервис Seminarlnfo, запустив приложение SeminarlnfoServlce.
Проверьте, что файлы juu-core.jar, juii-ext.jar и sun-util.jar заданы в переменной
окружения С LASS PATH и введите следующую команду в командной строке:
Java -classpeth %CLASSPATH%; SeminarService.jar
-Djava.security.роИ.су=политика
-Djava.rmi.tarver.codebase=http://хост:9090/
SeminarServiceDownload.jar
с от.deitel.adrjhtpl.jini.seminar.service.SeminarlnfoService
где политика — соответствующая политика безопасности, а хост — имя хоста, на
котором выполняется Web-сервер для загрузки посредника сервиса Jini. He
забудьте задать надлежащий номер порта для Web-сервера, который обслуживает
архив SeminarServtceDownload.jar (например, 9090).
Запустите клиент Jini Seminarlnfo, запустив приложение UnicastSeminarlnfo-
Service. Проверьте, что в переменной окружения CLASSPATH указаны файлы
Jini
117
jini-core.jar. jini-ext.jar и sun-ntil.jar, и что в переменной окружения CLASSPATH
не указан ни один из файлов классов сервиса Semlnarlnfo. Введите следующую
команду в командной строке:
java -classpath %CLASSPATH%; SeminarClient.jar
-Djava.security .роИ.су=политика
Com.deitel.advjhtpl. jini.seminar,client
OnicastSeminar InfoCllent хост
где политика — соответствующая политика безопасности, а хост — имя хоста
компьютера, предоставляющего сервисы поиска. На рис. 3.27 показан результат
работы приложения UnicastSemmarlnfoClient.
„а. Типичная ошибка программирования 3.4
i?fp Если поместить JAR-файл или классы для сервиса Semiaarlnfo в перемен-
L23J—J ную окружения CLASSPATH клиента Jini, клиент не сможет
загружать файлы классов по сети.
Рис. 3.27. Результат работы ..?.:•■!—•>.■-.■'■■■ UnkastSeminarlnfoCllent
3.8. Знакомство со вспомогательными утилитами
высокого уровня
Бели воспользоваться рассмотренной ранее технологией, можно построить
полноценный сервис на основе Jini. Однако вспомогательные утилиты Jini упрощают
процесс разра5отки приложений Jini. Эти вспомогательные утилиты
предоставляют высокоуровневые возможности управления. В данном разделе вы увидите, как
эти утилиты упрощают разработку и использование сервисов Jini.
3.8.1. Утилиты обнаружения
Как вам уже известно, до того, как клиент кли сервер сможет
взаимодействовать с сервисом поиска, он должен сначала обнаружить сервис поиска.- В примере
на рис. 3.13 мы уже познакомились с высокоуровневой утилитой обнаружении
LookupDiscovery. В этом разделе будут рассмотрены две высокоуровневые
утилиты обнаружения: класс net.jiiii.discovery.LookupLocatorDiscovery и класс netji-
ni.discovery. LookupDiscovery Manager.
118
Глава 3
Утилита LookupLocatorDiscovery
В примере UnicastDiscovery (рис. 3.10) мы использовали класс LookupLoeator
для обнаружения сервисов поиска на известном хосте. Чтобы использовать эту
технологию для обнаружения сервисов поиска, расположенных на нескольких
известных хостах, потребуется нескольких объектов LookupLoeator — по одному
для каждого хоста, предоставляющего сервис поиска. Класс
LookupLocatorDiscovery дает возможность сервису или клиенту Jim легче обнаруживать сервисы
поиска на нескольких известных хостах. Класс LookupLocatorDiscovery
использует объекты Discovery Event для уведомления сервиса или клиента Jini об
обнаруженных сервисах поиска. Эта задача аналогична задаче, которую выполняет класс
Lookup Discovery при обнаружении с помощью группового вещания.
Класс UnlcastDiscoveryUtility (рис. 3.23) использует класс
LookupLocatorDiscovery для обнаружения сервиса поиска на нескольких известных хостах путем
однонаправленного вещания. Класс UnlcastDiscoveryUtility реализует интерфейс
Discovery Listener для получения событий DiscoveryEvent от объекта
LookupLocatorDiscovery.
Конструктор UnlcastDiscoveryUtility (строки 31-62) принимает в качестве
параметра строковый маосив, содержащий список URL jini:, на которых будет
выполняться обнаружение с помощью однонаправленного вещания. В строках 42-43
создается массна объектов LookupLoeator, а в строках 49-50 создается объект
LookupLoeator для каждого URL в массиве urls. В строке 53 осуществляется
регистрация объекта UnicastDiscovery Utility в качестве слушателя DiscoveryListener
для объекта LookupLocatorDiscovery.
1 // OnicaatDiscoveryOtility.java
2 // демонстрация обнаружение нескольких сервисов поиска
3 // с помощью утилит LookupLocatorDiscovery
4 package com.deitel.advjhtpl .jin'i.utilities.discovery ;
5
6 // Набор базовых пакетов Java
7 import java.rmi.*;
8 import Java.io.*;
9 import java.awt.*;
10 import java.awt.event.*;
11 import java.net.*;
12
13 // Пакет Swing
14 import javax.swing.*;
15
16 // Набор базовых пакетов Jini
17 import nat.jini.core.lookup.ServiceRegistrar;
18 import net.jini.core.discovery.LookupLoeator;
19
20 // Пакеты расширений Jini
21 import net.jini.discovery.LookupLocatorDiscovery;
22 import net.jini.discovery.DiscoveryListener;
23 import net.jini.discovery.DiscoveryEvent;
24
25 public class UnlcastDiscoveryUtility extends JFrame
26 implements DiscoveryListener (
27
28 private JText&rea eutputArea = new JTextArea{ 10, 20 );
29
30 // конструктор OnicastDiscoveryOtility
31 public UnicastDiscoveryOtility( String urls[] ]
super( "UnicastDiscoveryOtility" );
getContentPaneO.add( new JScrollFane( outputArea ),
BordarLayout.CENTER );
// обнаружение сервисов поиска с помощью
// LookupLocatorDiscovery
try (
// создание объекта LookupLocator для каждого ORL
LookupLocator locators[[ =
new LookupLocator( urls.length ] ;
for ( int i = 0; i < locators.length ; i++ )
locators( i ] = new LookupLocator( urls[ i ] );
// создание объекта LookupLocatorDiacovery
LookupLocatorDiscovery locatorDiscovary =
new LookupLocatorDiscovery( locators );
// рехтсстрацкн слушателя DiscovexyListener
loeatorDiscovery.addDiacoveryliistanert this ) ;
57 // обработка некорректно!1© ORL Jini
58 catch ( HalformedORLException exception ) {
59 exception.printstackTrace () ,■
60 }
61
62 } // конец конструктора UnicastDiecoveryOtility
63
64 // получение уведомления о найденных сервисах поиска
65 public void discovered( OiscoveryEvent event )
66 (
67 // получение уполномоченных регистраторов дня этих сервисов
68 ServiceRegistrar[) registrars = event.getRegistrars();
69 // отображение информации дня каждого найденного
70 // сервиса поиска
71 for ( int i = 0; i < registrars.length ; i++ )
72 displayServiceDetails( registrars[ i ] );
73
74 ) // конец метода discovered
75
76 // отображение информации дня каждого регистратора SexviceRagistrar
77 private void displayServiceDetails( ServiceRegistrar registrar )
78 {
79 try (
80 final StringBuffer buffer - new StringBuffer();
81
82 // получение имени хоста и номера порта
83 buffer.append( "Lookup Service: " );
84 buffer.append( "\n Host: " +
85 registrar.getLocator().getHost() );
86 buffer.append( "\n Port: " +
87 registrar.getLocator().getPort() > ;
88 buffer.append( "\n Group support: " );
89
90 // получение групп сервисов поиска
91 String[] groups = registrar.getGroupsО;
92
93 // получение имей групп; если пусто, присвоить public
94 for ( int i = 0; i < groups.length ; i++ ) (
95
96 if ( groups( i ].equal*( "" ) )
97 buffer.append( "public," );
98
99 else
100 buffer.append( groups[ i [ + "," );
101 )
102
103 buffer.append( "\n\n" );
104
105 // добавление информации я область outputArea
106 SwingUtilities.InvokeLateг(
107
108 // создание объекта типа Runnable для добавления текста
109
110
111 // добавление текста и обновление позиции курсора ввода
112 public void run()
113 {
114 outputArea.append( buffer.toStringO );
115 outputArea.setCaretPosition(
116 outputArea.getTe*t(>.length() >;
117 ]
118 )
119
120 ); // конец обращения к invokeLater
121
122 ) // конец блока try
123
124 // обработка моклвчения при взаимодействии с сервисом поиска
125 catch ( RemoteException exception ) (
126 exception.printSteckTrace();
127 >
128
129 } // конец метода displayServiceDetails
130
131 // игнорировать отвергнутые сервисы поиска
132 public void discarded( DiscoveryEvent event ) ()
133
134 // sanycx приложения UnicastDiacoveryUtility
135 public static void roain( String args[] )
136 (
137 // установка менеджера SecurityManager
138 if [ System.getSecurityHanager() = null )
139 System.setSecurityManager( new RMXSecurityManager() );
140
141 // проверка наличия имени хоста в сараматрах командной строки
142 if ( arge.length < l ) {
143 System.err.printin(
144 "Osage: Java UnicaetDiscoveryUtility " +
145 "jini;//hostname:port [ jini;//hoetname;port. ] ..." );
146 }
147 // запуск утилиты OnicastDiscoveryQtility
148 // для задания имев хостов
149 else I
150 UnicastDiscoveryOtility unicestUtility =
151 new UnicastDiscoveryOtility( arg» );
152
153 unicastUtility.setDafaultCloseOperation( EXIT_ON_CLOSE );
154 unicastOtility.setSi»( 300, 300 > ;
155 unicaatUtility.setVisible( true );
156 }
157
158 ) // конец метода main
159 )
Рис. 3.28. Класс Unicast Discovery Utility использует класс LookupLocatorDiscovery для
облегчения обнаружений сервисов поиска
Класс LookupLocatorDiscovery вызывает метод discovered (строки 65-74),
когда обнаруживает новые сервисы поиска. В строке 68 извлекается массив
регистраторов ServiceRegistrar из объекта DiscoveryEvent, а в строке 72 вызывается
метод displays erviceDetails, чтобы отобразить информацию об обнаруженных
сервисах поиска. Метод display ServiceDetails (строки 77-129) помещает информацию
о хосте, порте и группе для объекта ServiceRegistrar в буфер StringBuffer (строки
83-103) и добавляет текст нз этого буфера в текстовую область outputArea (строки
106-120).
Чтобы продемонстрировать работу приложения Unicast Discovery Utility на
одном компьютере, запустите несколько экземпляров сервиса поиска Jini. Помните,
что нужно указать различные каталоги журнала для каждого экземпляра Reggie.
Запустив несколько экземпляров Reggie, выполните приложение Multicast
Discovery, представленное на рис. 3.13, чтобы получить различные номера портов, на
которых выполняется Reggie. Напомним, что утилита Unicast Disco very Utility
использует обнаружение с однонаправленным вещанием, следовательно, нужно
указать имена хостов и номера портов для компьютеров, на которых выполняются
сервисы поиска. Использование приложения Multicast Discovery — простой способ
определить эти имена хостов и номера портов для тестируемого класса UnicastDis-
со very Utility. На рис. 3.29 показан результат работы приложения Multicast-
Discovery Utility с четырьмя сервисами поиска, выполняющимися на локальной
машине. Обратите внимание, что сервисы поиска используют различные номера
портов. Мы используем эти номера портов при задании URL jini: для приложения
U nicast Dis coveryUtility.
После выполнения приложения MulticastDiscovery для нахождения
имеющихся сервисов поиска, введите следующую команду, чтобы запустить приложение
UnlcastDis с о very Uti lity:
java -Djava.security.policy=политика
com.deitel.advjhtpl.jini.utilities.discovery.
UnicastDiscoveryUtility
jini://xocr:3249 jini://хост:3257 jini://хост:3240
jini://xocr:4160
где политика — соответствующая политика безопасности, а хост — имя хоста,
выполняющего сервисы поиска. Помните, что необходимо заменить номера портов
в указанной выше команде на номера портов, которые приложение Multicast-
Discovery нашло на вашем компьютере. На рис. 3.30 показан результат работы
приложения UnicastDiscoveryUtility.
Ого up support, public
.ookup Same* 3
HottXXXX
„ookup senfea 4
Н0ПХХХХ
Рис. 3.29. Использование приложения MufticastDlscovery для получения данных для
тестирования приложения UnkastDlscoveryUtility
Group support public.
•Ookun 8аМсв:
HuStXXXX
Port 325;
Oraup support public,
лакир8*Мск
HostXXXX
Port 3240
Oroup support public.
jjokup Stroke
HostXXXX
Port 41(0
Group «upport public.
Рис. 3.30. Результат работы приложения UnkartDiscovcryUtility
Утилита LookupDitcoveryManager
Класс LooknpDiecoreryMaiiager предоставляет возможность гибкого
обнаружения сервиса поиска, позволяя приложениям и клиентам Jinj выполнять как
однонаправленное, так и групповое обнаружение сервиса поиска с помощью одного
класса. Класс LookupDiscoveryHeaager объединяет функциональные возможно-
Jini
123
сти, имеющиеся в классах Look up Locator Discovery (для обнаружения с помощью
однонаправленного вещания) и LookupDiscovery (для обнаружения с помощью
группового вещания).
Класс Gen era] Disco very Utility (рис. 3.31) выполняет обнаружение с помощью
однонаправленного и группового вещания с использованием класса
LookupDiscoveryManager. В строках 38-51 создаются и размещаются два компонента JText-
Area: один для отображения уведомлений индивидуального обнаружения, второй
для отображения уведомлении группового обнаружения. В строках 55-60
создается массив объектов Lookup Locator и заполняется массив URL из параметров
командной строки. В строках 63-64 создается новый объект LookupDiscovery-
Manager. Конструктор LookupDiscovery Ma «age r принимает в качестве первого
параметра массив имен групп для выполнения обнаружения с помощью
группового вещания. Константа Disco very GroupManagement.ALLG ROUPS указывает,
что менеджер LookupDiscovery Manager должен обнаруживать все сервисы
поиска. Эта константа эквивалентна передаче null в качестве первого параметра. Если
первым параметром является пустой строковый массив, обнаружение с помощью
группового вещания не выполняется. Вторым параметром конструктора Lookup-
Discovery Manage г является массив объектов LookupLocator для выполнения
обнаружения с помощью однонаправленного вещания. Если массив LookupLocator
равен null илн пуст, обнаружение с помощью однонаправленного вещания не
выполняется. Последним параметром является слушатель DiscoveryListener,
которому следует посылать уведомления об обнаружении.
Метод discovered (строки 30-101) получает уведомление об обнаружении от
менеджера LookupDiscoveryManager. В строке 33 извлекается массив обнаруженных
регистраторов ServiceRegistrar из объекта Discovery Event. В строке 89 вызывается
метод getFrom класса LookupDiscoveryManager, чтобы определить, какой способ
обнаружения — с помощью индивидуального или группового вещанни —
использовался при обнаружении данного регистратора ServiceRegistrar. Константа Lookup-
Disco very Manager .FROM_GROUP (строка 90) идентифицирует регистраторы
ServiceRegistrar, обнаруженные в ходе группового вещания. Если регистратор
ServiceRegistrar был обнаружен посредством группового вещания, в строках 92-93
вызывается метод displayserviceDetails для отображения информации о
регистраторе ServiceRegistrar в текстовой области multicast Area. Если регистратор Service-
Registrar не был обнаружен посредством группового вещания, в строке 98
вызывается метод displays erviceDetails для отображения информации о регистраторе
ServiceRegistrar в текстовой области unicastArea.
1 // GeneralDiscoveryUtility.Java
2 // GeneralDiacoveryUtility демошетрируав использование класса
3 // LookupDiscoveryManager для выполнения обнаружения с
Л // помощью однонаправлекаого и группового ■есажкк.
5 package com.deital.advjhtpl.jini.utilities.discovery;
6
1Ц Набор баковых пакетов Java
8 import java.rmi.*,-
9 import java.io.*;
10 iHport java.awt.*;
11 import java.awt.event.*;
12 import java.net.*;
13
14 // Стандартные расширения Java
15 import javax.swing.*;
16 import javax.swing.border.*;
17
124
Глава 3
18 // Набор б&аовнх пакетов Jini
19 import net.jini.core.lookup.ServiceRegistrar;
20 import net.jini.core.discovery.LookupLocator;
21
22. 11 Пакета! расннрежни Jini
23 import net.jini.discovery.*;
24
25 public class GeneralDiscoveryUtility extends JFrame
26 implements DiscoveryListener (
27
28 private LookupDiscoveryManager lookupManager;
29 private JTextArea multicastArea = new JTextArea( 15, 20);
30 private JTextArea unicastArea = new JTextArea( 15, 20 );
31
32 // конструктор GeneralDieooveryUtility
33 public GeneralDiscoveryUtility( String url»[] )
34 (
35 super( "GeneralDiecoveryOtility" ) ;
36
37 // размещение коиповектов JTextArea
38 JPanel multicastPanel = new JPanel();
3 9 multicastPanel.setBorder(
40 new TitledBorder [ "Multicast (Group) Notifications" ) );
41 multicastpanal.add( new J5crollPane( multicastArea ) );
42
43 JPanel unicastPanel « new JPanel();
4 4 unicastPanel.setBorder(
45 new TitledBorder( "Unicaet (Locator) Notifications" ) );
46 unicastPanel.add( new JScrollPane[ unicastArea ) );
47
48 Container contentPan* = getContentPane();
49 contentPan*.setLayout( new FlowLayoutO );
50 contentPane.add( unicastPanel );
51 contentPane.add( multicastPanel );
52
53 // получение объектов LookupLocator и LookupDiscoveryManager
54 try (
55 LookupLocator locators[] =
56 new LookupLocator[ urls.length ];
57
58 // получение массива объектов LookupLooator
59 for ( int i = 0; i < urls.length ; i++ )
60 locators[ i | « new LookupLocator( url»( i ] );
61
62 // создание змшшпяра LookupDiscoveryManager
63 lookupManager » new LookupDiscoveryHanager(
64 DiscoveryGroupHanagement.ALL_GBOuTS, locators, this );
65 )
66
67 // обработка некорректного URL Jini
68 catch ( MalformedURLException exception ) (
69 exception.printStackTrace();
70 )
71
72 // обработкл исключения при создании объекта
LookupD iscoveryManager
catch ( lOException eicaptlon ) {
exception.printStackTracef);
} // конец конструктора GeneralDiscoveryUtility
79 // получение уведомлений об обтаруженных сервиса:
80 public void discovered! DiscoveryEvent event )
81 {
82 // получение уполномоченных регистраторов для этих сервисов
83 ServiceRegistrar[] registrars = event.getRegistrars();
81
85 // обнаружение информации для каждого найденного сервиса поиска
86 for ( int 1=0; i < registrars.length ; i++ ) (
87 // отображение результатов группового обнаружения
88 // в mnlticastArea
89 if ( lookupManager.getFrom( registrars[ i ] ) =
90 LookupDiscoveryManager.PROM_GROUP ) (
91
92 displayServiceDetails( registrars[ i ],
93 multicastAraa );
94 1
95 // отображение результатов однонаправленного обнаружения
96 // в unicastArea
97 else
98 displayServiceDetails ( registrars [ i ] , unicastArea ) ,-
99 }
100
101 ) // конец нетода discovered
102 // отображение информации дня заданного регистратора
103 // ServiceRegistrar
104 private void diaplayServiceDetails(
105 ServiceRegistrar registrar, final JTextArea outputArea )
106 (
107 try (
108 final StringBuffer buffer = new StringBuffer () ;
109
110 // получение имени хоста и номера порта
111 buffer.append( "Lookup Service: " );
112 buffer.append( "\n Host: " +
113 registrar.getbocator().getHost() );
114 buffer.append( "\n Port: " +
115 registrar.getLocatorO,getPort() );
116 buffer.append( "\n Group support: " );
117
118 // получение групп сервисов поиска
119 String[] groups = registrar.getGroups();
120
121 // получение имен групп; если пусто, Задать public
122 for ( int i = 0; i < groups.length ; i++ ) (
123
124 if ( groups[ i ].equals( "" ) )
125 buffer.append( "public," );
126
127 else
12B buffar.append( group»[ i ] + "," ) ,-
129 )
130
131 boffer.append( "\n\n" );
132
133 // добавление информации в область outputArea
134 SwingUtilities.invokeLater(
135
136 // создался» объекта Runnable для добавления текста
137 пей Runnable (> (
138
139 // добавление текста и обновление позиции курсора ввода
140 public void run()
141 [
142 outputAraa.append( buffer.toStringO );
14 3 outputAraa.setCaretFosition(
144 outputArea.getTextt).length.(> );
145 }
146 )
147
148 ) ; // конец обращают к invoke!*ter
149
150 ) // конец блока try
151
152 // обработка, исключение при взаимодействии с сереисои поиска
153 catch ( Р—оtattxcaption exception ) [
154 exception.printSteckTraceO;
155 >
156
157 ) // конец метода displaySarviceDetailв
158
159 // получение уведомлений об отвергнутых сервисах поиска
160 public void di>cacded[ DiecoveryEvent event ) ()
161
162 // запуск приложения GeneralDiscoveryUtility
163 public static void xain( String(] arge )
164 (
165 // установка менеджера SeourityManager
166 if ( system.getSecurityManag»r[) = null )
167 System.setSacurityHanager( пем RMXSecurityHanager() );
168 // запуск утилиты GeneralDiscoveryUtility для задания
170 GeneralDiscoveryUtility utility -
171 new GeneralDiacoveryUtility( arge );
172 utility.eetDefaultCloseOperationt EXIT_OH_CLOSS > ,-
173 utility.pack();
174 utility.setVisiblet true );
175
176 ] // конец метода main
177 )
Рис. 3.31. Утилита GeneralDiscoveryUtility использует класс LookupDtecoveryManager для
выполнения обнаружения как с помощью однонаправленного, так и группового вещания
Jini
127
Метод dlspl ay ServiceDe tails (строки 104-15Э) принимает в качестве
параметров объекты ServiceRegistrar и JTextArea, в котором следует отображать
информацию об регистраторе ServiceRegistrar. В строках 111-131 в буфер StringBuffer
добавляется информация о регистраторе ServiceRegistrar. В строках 134-148
вызывается статический метод iDvokeLater класса SwingUtilities для добавления
информации о регистраторе в соответствующую текстовую область JTextArea.
Метод main (строки 163-176) задает менеджер RBHSecurityManager (строка
167) и создает новый экземпляр класса GeneralDiscoveryUtility, передавая массив
параметров командной строки конструктору (строки 170-171). Если пользователь
не предоставил каких-либо URL jini: в качестве параметров командной строки,
класс GeneralDiscoveryUtility не будет выполнять обнаружение с
однонаправленным вещанием. Чтобы выполнить утилиту GeneralDiscoveryUtility, введите
следующую команду в командной строке:
Java -Djava.security.ро1\су=политика
com.deital.advjhtpl.jini.utilities.disoovery.
GeneralDiscoveryOtility jini://хост:4160
где политика — соответствующая политика безопасности, хост — имя хоста для
известного компьютера, выполняющего сервис поиска, а 4160 — номер порта
сервиса поиска по умолчанию. На рис. 3.32 показан результат работы приложения
GeneralDiscoveryUtility с несколькими сервисами поиска, выполняющимися на
локальном компьютере.
Рис. 3.32. Результат работы приложения GeneralDiscoveryUtility
3.8.2. Информационные утилиты
Атрибуты Entry задают характеристики сервисов Jini. Мы использовали
атрибут Name в примере на рис. 3.21, чтобы предоставить имя для сервиса Semi-
narlnfo Jini. Атрибуты Entry помогают клиентам идентифицировать сервисы Jini.
Присоединяя атрибуты к сервисам, поставщики сервисов могут публиковать
сервисы с подробной информацией о ник, включая местоположение сервиса и
функциональные возможности сервиса. Jini предоставляет семь типовых атрибутов
(рис. 3.33).
Атрибут
ш...
Comment
Описание
Задает физическое местоположение сервиса (например, город
и название улицы для банкомата).
\ Задает общее описание сервиса.
128 Глава 3
Атрибут
Location
■■—
Servi celn fо
ServiceType
Status
Описание
Задает более подробную информацию о местоположении, такую как этаж
и номер офиса.
Имя, которое будет идентифицировать сервис (например, «ВапкАВС
ATM»).
Предоставляет базовую информацию о сервисе. Например,
производителя и модель принтера.
Предоставляет понятное для человека описание типа сервиса (например,
«Очередь на печать»).
Описывает текущее состояние сервиса.
Рис. 3,33. Стандартные информационные атрибуты Jini
Разработчики также могут создавать собственные атрибуты для сервисов Jini.
Класс SeminarProvider (рис. 3.34) представляет собой атрибут типа Entry,
который задает наименование компании, проводящей семинары, для данного сервиса
Seminarlnfo. Класс SeminarProvider расширяет класс AbstractEntry и реализует
интерфейс ServiceControlled. AbstractEntry представляет собой базовую
реализацию интерфейса Entry. Реализацией интерфейса ServiceControlled класс Seminar-
Provider указывает, что сервис сам управляет атрибутом SeminarProvider.
Класс Entry должен предоставлять конструктор без параметров (строка 12).
Переменные экземпляров должны являться открытыми ссылками на объекты типа
Serializable, чтобы клиенты могли выполнять поиск с использованием этих
переменных экземпляров. В етроке 12 объявлена строковая открытая переменная рго-
viderName, которая оодержит наименование организации, проводящей семинары.
Чтобы воспользоваться атрибутом SeminarProvider для нашего сервиса
Seminarlnfo Jini, нам нужно модифицировать класс SeminarlnfoService, который
регистрирует сервис Seminarlnfo с помощью регистраторов ServiceRegistrar.
Замените строку 43 листинга на рис. 3.21 строкой
entries[ D | ■ пом
com. deitel .advjhtpl.jini.utilities,entry,SeminarProvider(
"Deitel");
Мы также должны модифицирогать класс UnicastSeminarlnfoClient, чтобы
осуществлять поиск объектов SeminarInfoServicee с использованием нового атрибута
SeminarProvider. Замените строку 110 листинга на рис. 3.22 строкой
Entry[] attributes new Entry[] ( new
com.deitel.advjhtpl.jini.utilities.entry.SeminarProvider (
"Deitel" ) );
1// SeminarProvider. Java
2 // SeminarProvider - объект типа Entry для сервиса Seminarlnfo.
3 package com.deitel.advjhtpl.jini.utilities.entry;
4
5 // Пакеты расширений Jini
6 import net.jini.entry.';
7 import net. jini.lookup.entry. *,-
8
9 public class SeminarProvider extends AbstractEntry
10 implements ServiceControlled
11 (
12 public String providerName = "";
13
Jini
14 // конструктор без парамефроя
15 public Semi narProvider() {}
16
17 // конструктор SeminarProvider для задания имени поставщика
provi derName
18 public SeminarProvider( String provider }
19 {
20 providerName = provider;
21 )
22 }
Рис. 3.34. Подкласс SeminarProvtder класса Entry для описания организации,
проводящей семинар, в атрибуте Jim
Откомпилируйте и выполните сервис Jini и приложение UnicastSeminarlnfo-
Client, чтобы найти сервис Seminarlnfo с помощью нового атрибута SeminarPro-
vider. He забудьте включить класс Seminar Provider, class в JAR-файлы сервиса
и клиента.
3.8.3. Утилиты аренды
Jini использует аренду, чтобы обеспечить целостность распределенных систем,
построенных с помощью Jini. Вспомним, что сервисы Jini регистрируются
сервисами поиска, чтобы сделать функциональные возможности сервиса Jini
доступными для других членов в сообществе Jini. Если все идет нормально, другие члены
сообщества Jini используют сервис, сервис остается работающим и выполняется
неопределенно долго. Однако в реальности сервисы по ряду причин выходят из
строя. Аварии в сети могут сделать сервис недоступным. Физическое устройство,
ассоциированное с сервисом (например, принтер), может нуждаться в ремонте.
В самом сервисе может произойти невосстанавливаемый сбой. В этих и во многих
других ситуациях сервис может стать недоступным, и этот сервис может оказаться
не в состоянии отменить свою регистрацию в сервисах поиска, чтобы
предотвратить попытки использования этого сервиса другими клиентами.
Одна из целей технологии Jini — сделать сообщество Jini сам овос стан
заливающимся и дать возможность разрешения типовых проблем, таких как сбои в сети,
аппаратные сбои и программные сбои. Таким образом, когда сервис Jini
регистрируется сервисом поиска, регистрация не является постоянной. Регистрация
арендуется на определенный период времени, после чего сервис поиска аннулирует
регистрацию. Тем самым предотвращается негативное влияние проблемных
сервисов на все сообщество Jini. Если сервис Jini отказывает, аренда сервиса Jini
заканчивается, и сервисы поиска больше не будут предоставлять отказавший
сервис клиентам Jini.
Стратегия аренды, которую применяет Jini, является жесткой, — если сервис
Jini не возобновляет аренду, сервис поиска завершает регистрацию, когда срок
аренды истекает, и сервис становится недоступным для клиентов. Следовательно,
разработчики должны обеспечить управление арендой регистрации для своих
сервисов Jini, чтобы не допустить преждевременного прекращения регистрации
сервисов.
Наш сервис Seminarlnfo Jini не выполняет какой-либо поддержки обновления
аренды. После истечения первого срока аренды сервиса Seminarlnfo (т.е. 10
минут), сервис Seminarlnfo больше не является доступным для клиентов. Сам сервис
продолжает выполняться, но сервисы поиска, в которых сервис зарегистрирован,
отменят регистрацию.
5 За» 204
130
Глава 3
Класс LeaseRenewalManager представляет собой вспомогательный класс Jini,
который дает возможность сервисам управлять арендой и делать так, чтобы аренда
сервисов не была преждевременно завершена. Класс SeminarlnfoLeaseService
(рис. 3.35) использует класс LeaseRenewalManager для управления арендой для
сервиса Seminarlnfo. Класс SeminarlnfoLeaseService схож с классом Seminar-
info Service (рис. 3.21), поэтому мы в етом примере сосредоточим внимание на
управлении арендой.
1 // SeminarXnfoLeaaeService-Java
2 // SemiaarlnfoLeasaService обнаруживает сервисы поиска,
3 // регистрирует сервис Seminarlnfo и создает менеджера
4 // LeaseRenewalManager для обслуживания аренды сервиса Seminarlnfo.
5 package сои.deitel.advjhtpl.jini.utilities.leasing;
б
7 // Набор базовых пакетов Java
8 import java.rmi.RMISecurityMaiiager;
9 import java.rtai.RemoteSxception;
10 import java.io.XOException;
11
12 // Набор базовых пакетов Jini
13 import net.jini.core.lookup.*;
14 import net.jini.core.entry.Entry;
15 import net.jini.core.leaee.Lease;
16
17 // пакеты расширении Jini
18 import net.jini.discovery.*;
19 import net.jini.lookup.entry.Name;
20 import net.jini.lease.LeaseRenewalManager;
21
22 // Пакеты Deitel
23 import ссы-deitel.advjhtpl.jini.seminar.service.*;
24 import com.deitel.advjhtpl.jini.utilities.entry.SeminarProvider;
25
26 public class SeminarlnfoLeaseService implements DiscoveryListener {
27
28 private LookupDisoovery discover;
29 private Service!tem item;
30 private static final int LBASETIHE - 10 * 60 * 1000;
31
32 // конструктор SeminarlnfoLeaseService
33 public SeminarlnfoLeaseServioe0
34 (
35 // обнаружение сервисов поиска ■ группе public
36 try (
37 discover = new LookupDiscoveryt new String[] { "" } };
38
39 // регистрация слушателя DiscoveryListener
40 discover.addDiacoveryListener( this };
41 }
42
43 // обработка исключения при создании обмята LookupDiscovery
44 catch ( IObtcaption exceptioa ) (
45 exception.printStackTracaf};
46 }
47
48 // задание ииаки Entry для сервиса
Entry[] entries - new Entry[ 1 ];
entries[ 0 ] = new SeminarProvider( "Deitel" };
// ведение посредника м содержимого сараиса
item = naw ServiceXtemf null, cre&teProxyO, entries };
[струхтора SeminarInfoLeaseServioe
// получение уведомлений об обнаруженных сервисах поиска
public void discovered [ DiscoveryBvent event }
(
ServiceRegistrar[] registrars = event.gatRegiatrara{};
// регистрация сервисов сервисов поиска
Cor ( int 1=0; i < registrars.length; i++ } {
ServiceRegistrar registrar = registrars! i ];
// регистрация сервиса сервисом поиска
try (
ServiceRegistration registration =•
registrar.register! item, LKASETIME );
// совданне нанеджера LeaseRenewalmanager
LeaseReneralHanager leaseHanager =
паи LeaseRenewalManager{};
// воаобноаление аренды SeminarInfo
// на веопределекнный срок
leaseHanager.ranawUntil( registration.getLease(},
Lease.FOREVER, null };
} // конец блоха try
// обработка исключения при регистрации объекта Servieeltem
catch { BemoteBxception exception } (
exception.printStackTrace{};
}
} // конец блока Cor
} // конец иетода discovered
// игнорировать отвергнута» сервисы поиска
public void discarded( DiscoveryBvent event ) (}
// соадаиие посредника сервиса жди семинара
private Seminar Interface createProxy(}
(
// получение ссылки Backendlnterface ка SeminarInfo
try (
Backendlnterface backlnterface = new Seminarlnfo{};
return new SeminarProxy( backlnterCaoe };
104 // обработка исключения ори создании объем» SeminarProxy
105 catch [ RamoteExoeption exception } {
106 exception.printstackTrace{) '■
107 }
ioe
109 return null;
110
111 } // конец метода createProxy
112
113 // запуск приложения SeminarlnfoLeaeeServiee
114 public static void main( String args[] )
115 (
116 // установка менеджера SecurityManager
117 if ( System.getSecurityHanagerО *= null ) {
116 System.setSecurityHanager( new BMISecurityManager() };
119 }
120
121 SeminarlnfoLeaseService service *
122 new SeminarlnfoLeaseServica0;
123
124 Object keepAlive = new ObjectO;
125 // использование объекта keepAlive ждя поддержания
126 // активного состояния сервиса
127 synchronised [ keepAlive ) {
128
129 // подержание активного состояния приложения
130 try (
131 keepAlive.wait(};
132 }
133
134 // обработка исключения при прерывании ожидания
135 catch ( InterruptedException exception } {
136 exception.printStackTrace();
137 }
138
139 } // конец блока synchronized
140
141 ) // конец метода main
142 )
Рис. 3.35. Класс SemmarlnfoLeaseService использует класс LeaseRenewalManager для
управления арендой сервиса Seminarlnfo
В методе discovered строки 69-70 регистрируют объект Serviceltem сервиса
Seminarlnfo с помощью обнаруженного регистратора ServiceBegistrar. В строках
73-74 создается новый объект LeaseRenewalManager, который класс
SeminarlnfoLeaseService использует для управления арендой сервиса Seminarlnfo.
В строках 77-78 вызывается метод renewllntil класса LeaseRenewalManager.
Метод renew Until прижимает в качестве своего первого параметра объект типа Lease,
подлежащий возобновлению. В этом примере мы получаем объект Lease, вызывая
метод get Lease класса service Registration. Второй параметр задает желаемое
время действия (в миллисекундах) для возобновления аренды Lease. В строке 78
задается константа Lease.FOREVER для запроса аренды Lease, которая никогда не
заканчивается. Это не гарантирует, что сервис поиска будет предоставлять вечную
аренду, -— сервис поиска может предоставить аренду, длительность которой будет
Jini
133
короче запрошенной. Третьим параметром метода renew Until является слушатель
Lease Listener, уведомляющий о проблемах, возникающих при воеобновлении
аренды. Мы передаем null в качестве третьего параметра, чтобы игнорировать
такие уведомления.
Чтобы выполнить сервис Jini Seminarlnfo с управлением арендой, создайте
новый JAR-файл с именем Seminar Service WithLeasing .jar. В таблице па рис. 3.36
показано содержимое архива Seminar Service WithLeasing. jar. Обратите
внимание, что этот JAR-файл заменяет класс SeminarlnfoServi ce. class на класс Semi'
narlnfoLeaseService.class. Обретите также внимание, что этот JAR-файл
включает класс Seminar Provider, class, который представляет собой наш
пользовательский объект типа Entry для сервиса Seminarlnfo.
После распаковки классов, содержащихся в архиве SeminarServiceWithLea-
sing.jar, запустите новую версию сервиса, введя следующую команду в командной
строке:
Java -classpath %CIASSPATH%; SeminarServieeWithLeasing.jar
-Djava. security.роИсу=политика
-Djava.rmi.server.codeb*se=bttp://хост:9090
SeminarServiceDownload.jar
com.deitel.advjhtpl.jini.utilities.leasing.
S eminarInfoLeaseService
где политика — соответствующая политика безопасности, а хост — имя хоста, на
котором выполняется Web-сервер для загрузки посредника сервиса Seminarlnfo.
Менеджер LeaseRenewalManager будет возобновлять аренду сервиса Seminarlnfo
для поддержания регистрации данного сервиса сервисом поиска.
3.8.4. Утилита Join Manager
Как мы видели, чтобы сделать сервис Jini доступным для сообщества Jini,
необходимо выполнить несколько действий. Сервис должен обнаружить сервисы
поиска, зарегистрировать обнаруженные сервисы поиска и поддерживать аренду
регистрации. Класс Join Manager представляет собой вспомогательный класс, который
упрощает процесс развертывания сервиса Jini, выполняя обнаружение сервиса
поиска, регистрацию сервиса и управление арендой в одном классе.
Файл класса
Seminar.clae s
SeminarInterface.clasa
SeminarProxy.class
Backendinterface.class
Seminarlnfo.class
Seminarlnfo Stub.clasa
SeninarFrovider.class
SeminarlnfoLeaseService. cla*e
Каталог в архиве SemlnarServkeWithLeasing.jar
com\deital\adVjhtpl\jini\8eminar\
com\deltel\advjhtpl\j ini\seminar\service\
coa\daitel\advjhtpl\jini\seminar\service\
com\deite1\advjhtp1\jini\semiпае\serviсе\
coa\deitel\advjhtpl\j ini\seminar\se rvice\
сои\deitel\«dvjhtpl\jini\seminar\serviсе\
ce»\deitel\advjhtpl\j ini\utilitiee\entry\
coa\deitel\advjhtpl\jini\
utilities\leaa ing\
Рис. 3.36, Содержимое архива SeminarServtceWlthLeaslng.jar
Класс SeminarlnfoJoinService (рис. 3.37) использует класс JoinManager для
развертывания сервиса Seminarlnfo. В строках 38-40 создается объект Lookup-
134
Глава 3
Disco very Manager, который класс JoinManager будет использовать для
обнаружения сервисов поиска. Мы передаем конструктору LooknpDiacoveryManager в
качестве первого параметра строковый массив с одним элементом, которым является
пустан строка. Этот параметр указывает, что объект LooknpDiscoveryManager
должен выполнять с помощью группового вещания обнаружение поисковых
сервисов, которые поддерживают группу public. В качестве второго и третьего
параметров в строке 40 передается значение null. Эти параметры, соответственно,
запрещают обнаружение с помощью однонаправленного вещания к задают пустой
(null) слушатель Discovery Listener. Утилита JoinManager сама управляет
процессом обнаружения, поэтому классу SeminarlnfoJomService не нужно обрабатывать
события DiscoveryEvent.
В строках 43-44 создается новый массив содержимого (Entry) с одним
элементом SeminarProvider, который задает поставщика объектов Seminar для сервиса
Seminarlnfo. В строках 47-49 создается новый экземпляр класса JoinManager '
для обнаружения сервисов поиска, регистрации сервиса и обслуживания аренды
регистрации сервиса. В строке 47 вызывается метод createProxy для создания
посредника Seminar Proxy для сервиса Seminarlnfo. Вторым параметром
конструктора JoinManager является массив атрибутов, которые описывает сервис. Третьим
параметром является ссылка на слушатель ServicelD Listener. Когда утилита
JoinManager регистрирует оервис Jini с помощью сервиса поиска, JoinManager
уведомляет слушателя ServicelDListeiier об идентификаторе сервиса, который
сервис поиска присваивает сервису Jini. Четвертым параметром является объект
класса DiscoveryManagement для обнаружения сервисов поиска. В этом примере
мы передаем объект LooknpDiscoveryManager, созданный в строках 38-40.
Последним параметром, передаваемым конструктору JoinManager, является объект
LeaseBenewalManager для поддержания аренды регистрации сервиса.
1 // SeminarlnfoJoinServioe.Java
2 // SeminarlnfoJoinService использует обмк* JoinManager для
3 // нахождения сервисов поиска, регистрации сервиса Seminar
4 // сервисами поиска и управления воэобиоадеяием аренды.
5 package coa.daitel.advjfatpl.jini.utilities.join;
б
7 // Набор бааовкх пакетов Java
8 import java.rtoi.RMXSecuxityM&nager;
9 import java.noi.RemoteZxception;
10 import java.io.IOException;
11
12 // Набор бавовнх пакетов Jini
13 import net.jini.core.lookup.ServicelD;
14 ijqport net.jini.core.entry.Entry;
15
16 // Пакеты ресширеиий Jini
17 import net.jini.lookup.entry.Han*;
18 import net.jini.lease.LeaseKenewelManager;
19 import net.jini.lookop.JoinManager;
20 import net.jini.discovery.LookupDiacoveryManager;
21 import net.jini.lookup.ServicelDLiatener;
22
23 // Пакет Deitel
24 import com.deitel.advjfatpl.jini.seminar.service.*;
25 import com.deitel.advjhtpl.jini.utilities.entry.*;
26
27 public сАаяа SeminarlnfoJoinService implementa ServicelDLiatener {
28
// коиструхтор SeminarlnfoJoinServiee
public SeminarlnfoJoinServiee (}
(
// исподъвонание объекта JoinManager для регастрацин
// сервиса SeminarInfo и управления арендой
try (
// создание менеджера LookupDiscoveryHanager для
// обнаружения сервисе» поиска
LookupDisoovaryHanager lookupHanager ■>
new LookupDiecoveryHanager( new String[] ( "" >,
null, null );
// задание имени содержимого Entry ждя сервиса
Entry[] entries » new Entry[ 1 ];
entries[ 0 ] = new Semin&rProvider( "Deitel" );
// создание объекта JoinKanager
JoinManager manager = new JoinManager( createProxy(},
entries, this, lookupHanager,
new LeaseRanewalKanager{} };
}
// обработка исключения при создании объекта JoinKanager
catch [ lOException exception } {
exception.printstackTrace{};
}
) // конец конструктора SeminarlnfoJoinServiee
// соадакме посредника сервиса ждя семинаре
private SeminarInterface createProxy()
(
// получение посредника SeminarProxy для сервиса Seminarlnfo
try (
return new SeminarProxy( new Seminarlnfo{} };
}
// обработка исключения при создают объекта SeminarProxy
catch [ RemoteExeeption exception } {
exception.printStackTrace0;
}
return null;
} // конец метода createProxy
// получение уведомления о присвоения ServiceID
public void 8ervioeIDHoti£y{ ServloelD serviceID }
(
System.err.printlnf "Service ID: " + servioelD );
// вапуск приложения SeminarInfoJoinServioe
public static void main( String args[] }
// установка менеджера SecurityHanager
if ( System.getSacurityManager() = null ) (
System.setSecurityKanager( new RMISecuritvManager{) };
// создание объекта SeminarinfoJoinService
new SeminarinfoJoinService(};
Рис. 3.37. Класс SeminarinfoJoinService использует iaiacc JoinManager для упрощения
регистрации сервиса Seminarlntb и управлении его арендой
Метод servicelDNotify (строки 77-80) связан с интерфейсом ServicelDListener.
Утилита JoinManager вызывает метод servicelDNotify, чтобы уведомить
слушателя ServicelDListener, что сервис поиска назначил идентификатор сервису Jinl.
В строке 79 просто выводится идентификатор сервиса.
Метод main (строки 83-92) устанавливает менеджер безопасности RMISecurity-
Manager и запускает приложение SeminarinfoJoinService. Обратите внимание, что
метод main не использует объект keepAlive класса Object, чтобы поддерживать
состояние выполнения приложения, как это было необходимо для предыдущего
примера. Утилита JoinManager сама заботится о выполнении приложения.
Чтобы выполнить сервис Seminar Info с помощью утилиты JoinManager,
создайте JAR-файл SeminarS erviceJoin Manager .jar с содержимым, представленным
в таблице на рис. 3.38.
Файл класса
Seminar.class
SeminarInterface.class
SeminarProxy.claaa
Backendlnterface.claaa
SeminarInfo.class
SeminarInfo Stub.class
SeminarProvider.class
SeminarinfoJoinService.class
Каталог в архиве SembiarServkeJotn Manager.|ar
com\deitel\advjhtpl\jini\seminar\
com\deitel\advjhtpl\jini\seminar\service\
com\deitel\advjhtpl\jini\aeminar\aervice\
com\deitel\advjhtpl\jinl\e«miner\servica\
com\de i tel\advjhtpl\jini\aaminar\a•rvioe\
aom\de1tel\advjhtpl\jini\seminar\aarviсе\
com\deitel\adVjhtpl\jini\utilitiea\entry\
com\dei tel\advjhtpl\j ini\utili ties\join\
Рис. 3.38. Содержимое архива SeminarServkeJolnManager.jar
После упаковки классов в архив Seminar Service JoinManager. jar выполните
новую версию сервиса, введя следующее в командной строке:
java -claespath %CLASSPATH%; SeminarServiceJoinManager.jar
-D Java, security .policy^nojornwa
-D java. rmi. server. coclebas«=http://хост: 9090/
SaminarServiceDownload.jar
com.deitel.advjhtpl■jini.utilities.join.
SeminarInfoJoinSarvice
где политика — соответствующая политика безопасности, а хост — имя хоста, на
котором выполняется Web-сервер для загрузки посредника сервиса Seminarlnfo.
Jini
137
На рис. 3.37 показан пример вывода идентификатора сервиса приложением
Seminar Info Join Service.
3.8.5. Утилиты обнаружения сервисов
Сложные клиенты Jini часто предъявляют специфические требования к
сервисам Jini, которые они применяют. Чтобы удовлетворить этим требованиям, клиент
Jini часто должен работать с наборами сервисов Jini. Клиент осуществляет поиск,
чтобы найти определенный сервис, который может удовлетворить его потребности.
Например, клиенту Jini, который предоставляет пользователям информацию
о принтерах, имеющихся в данном офисе, понадобится набор сервисов для
имеющихся принтеров. Этой программе мониторинга принтеров не нужно знать о
состоянии каждого из принтеров при добавлении нового принтера. Сервис также
должен иметь возможность определять характеристики принтеров (поддержка
цветной печати, качество печати, емкость, быстродействие и т.д.).
Класс net.jini.lookupJServiceDiscoveryManager предоставляет клиентам Jini
богатый набор сервисов и функций управления сервисом поиска, которые
предоставляет интерфейс ServiceRegistrar. Класс ServiceDiscoveryManager облегчает
обнаружение доступных сервисов и дает возможность клиентам выполнять
избирательный поиск, который воеможен для интерфейса ServiceRegistrar. Клиенты
Jini также могут использовать класс ServiceDiscoveryManager, чтобы улучшить
производительность приложения, обслуживая локальный кэш сервисов. Имеются
три основных действия, в которых клиенты Jini используют класс
ServiceDiscoveryManager: создание локального кэша ресурсов, получение уведомлений
о событиях, когда сервисы становятся доступными или недоступными, и
выполнение детализированного поиска, который невозможен при использовании простых
шаблонов ServiceTemplate.
Класс ServiceDisco very Manager может улучшить производительность клиента
Jini путем создания локвльного кэша обнаруженных сервисов. Этот локаньный
кэш, реализованный как интерфейс new.jiiii.lookup.LookupCache, дает
возможность клиенту выполнять дополнительный поиск сервисов, не потребляя сетевых
ресурсов, связанных с удаленным вызовом сервиса поиска. Когда клиенту нужен
определенный сервис, который находится в кэше LeokupCaehe, клиент просто
вызывает метод' lookup интерфейса LookupCache для извлечения сервиса из
локального кэша.
Клиенты Jini также могут использовать кэш LooknpCache, извлеченный из
объекта ServiceDieco very Manage г, чтобы получать уведомления, имеющие
отношения к набору сервисов. Реализуя интерфейс ServiceDiscoveryListener и
регистрируя кэш LooknpCache, клиент Jini может получать уведомления о событиях,
указывающие на обнаружение определенного сервиса, изменение атрибутов
сервиса, удаление сервиса из кэша LookupCache. Такое уведомление о событии особенно
полезно для клиентов Jini, которые управляют имеющимися ресурсами,
например, для системы мониторинга принтеров.
Класс ServiceDisco very Manage г также предоставляет усовершенствованный
интерфейс, который дает возможность клиентам Jini осуществлять поиск сервисов
с помощью более специфичных критериев поиска. Клиенты Jini могут
использовать класс ServiceDiscoveryManager с реализациями интерфейса Serviceltem-
Filter для нахождения сервисов, значения атрибутов которых лежат в пределах
определенного диапазона. Например, клиент Jini может использовать интерфейс
ServiceltemFilter для нахождения в районе всех автоматических кассовых
машин, плата за пользование которыми не превышает двух долларов. Подобный
специфический запрос невозможен при использовании стандартного шаблона
ServiceTemplate, доступного через интерфейс ServiceRegistrar.
138
Глав* 3
Более подробную информацию о классе ServiceDtocoveryManager можно найти
в документации по Jini, включенной в состав пакета Jini Technology Core
Platform.
3,9. Ресурсы в Internet и во Всемирной паутине
www.jini.erg
Основной сайт сообщества Jini.
www.»un.сое/jini/ap«ca/jlnil.lhtal/coxaZOC.html
Сайт, содержащий спецификацию Jini Technology Core Platform Specification.
www.aun.co^jini/*pM*/jinil.lht»I/coll*ctionZOC.ht»I
Этот сайт содержит коллекцию утилит Jini Technology Helper UtlUtles я спецификаций
Service» Specification.
www.run.eoe/jini/»p*ce/jinil.lht»l/j»TOC.ht»l
Этот сайт предоставляет спецификацию JaoaSpace» Service Specification.
davaloper. Java. eun. coa/dav«lop«r/product>y jini/installation. index/html
Этот сайт предоставляет инструкции по установке Jini.
Резюме
• Многие сетевые устройства предоставляют сервисы клиентам сети.
• Каждый сервис имеет четко определенный интерфейс.
• Чтобы воспольэозаться сервисом, клиент должен уметь обнаруживать существование
сервиса и знать интерфейс для взаимодействия с сервисом.
• Jini расширяет RMI для предоставления сервисов в сети.
• Сервисы Jini построены по принципу «подключил и работай» — клиенты могут
обнаруживать сервисы в сети динамически, прозрачно загружая классы, необходимые для ис-
□ользованна этих сервисов, а затем приступать к взаимодействию с этими сервисами.
• Возможность динамической загрузки класса RMI позволяет клиентам Jini использовать
сервисы, не устанавливая заранее специальные программные драйверы для этих сераи-
• Для того чтобы клиенты могли обнаруживать и использовать сервисы Jini, должны быть
разработаны стандартные интерфейсы для типовых сервисов.
• К базовому программному обеспечению Jini относятся Java 2 Standard Edition (J2SE)
и Jini Technology Starter Kit. Если вы собираетесь писать коммерческие сервисы Jini
и хотели бы протестнрозать их на совместимость с клатформой Jini, вам также
необходимо загрузить пакет Jini Technology Core Platform Competlbility Kit (Jini TCK}.
■ В ссотав Jini Starter Kit входят три компонента; Jini Technology Core Platform (JCPJ, Jini
Technology Extended Platform (JXP) и Jini Software Kit (JSK). JSK содержит основные
интерфейсы и классы Jini. JXP предоставляет вспомогательные утилиты для реализации
сервисов и клиентов Jini. JSK содержит реализацию сервисов, заданных в JCP и JXP-
• Чтобы откомхшлирозать и выполнить сервисы и клиенты Jini, необходимо включить
JAR-файлы jini-eorajar, jinl-extjer и анп-ntil.jer в переменную окружения CLASS-
PATH. Эти три JAR-фаала содержатся в каталога lib пакета Jini Starter Kit — оии
относятся, соответственно, к компонентам Jini Technology Core Platform, Jini Technology
Extended Platform и Jlni Software Kit.
• В дистрибутив Jini входят три сервиса, которые должны быть корректно запущены перед
выполнением приложений: Web-сервер, дающий возможность клиентам Jini загружать
файлы классов с помощью RMI, чтобы клиенты могли динамически осуществлять доступ
к сервисам Jini; демон аятивапии RMI (rmld), чтобы подключить инфраструктуру RMI.
позволяющую клиентам Jini взаимодействовать с сервисаии Jini; и сервис поиска для
предоставления информации о доступных сервисах Jini, дающий возможность клиентам
обнаруживать и исоользозать эти сервисы. Web-сервер и nnid должны быть запущены
(в любом порядке) перед запуском сервиса поиска.
• Реализация Jini Technology Core Platform включает утилиту StartServlce для запуска
необходимых сервисов.
Jini
139
• Сервис поиска Jini является основой Jini. Процесс нахождения сервисов и получения
ссылок ка них называется обнаружением.
• Обнаружение является отличительной чертой технологии Jini по сравнению с RMI. В
RMI нужно заранее знать, где находится объект. В Jini не нужно знать «где» — нужно
знать «как». Процесс обнаружения определяет, где расположен сервис, во скрывает
детали от разработчика.
• Обнаружение может осуществляться либо с помощью однонаправленного, либо
группового вещания.
• Обнаружение с помощью однонаправленного вещания, или докаторное обнаружение, дает
возможность сервису или клиенту Jini обнаруживать сервисы на определенном хосте.
• Метод getRegistrar иласса LooknpLocator выполняет обнаружение с помощью
однонаправленного вещания. Метод возвращает объект ServiceRegistrar, который представляет
сервис поиска. Перегруженная версия метена getRegistrar принимает в качестве
целочисленного параметра максимальное время ожидания (в миллисекундах), по истечении
которого выдается тайм-аут.
• Методы getHost и getPort класса LooknpLocator извлекают имя хоста (IP-ел рее) и номер
порта, где был обнаружен сервис поиска.
• Обнаружение с помощью группового вещзния, или групповое обнаружение, дает
возможность сервису или клиенту Jini обнаруживать сервисы поиска, когда конкретный хост, на
котором выполняется сервис поиска, неизвестен. Запрос группового обнаружения
использует групповое вещание для обнаружения ближайших сервиосв поиска. Сервисы поиска
периодически выдают объякаения, чтобы уведомить заинтересованные сервисы и
клиентов Jini о своем присутствии и доступности для использования.
• Класс netjini. discovery. Look apDiscovery выполняет обнаружение с помощью группового
вещания.
• Реалнаация интерфейса DiscoveryListener дает возможность объекту класса получать
события DisoeveryEvent — уведомления об обнаруженных сервисах поиска.
• Клаос LookupDiscovery вызывает метод discovered, когда объект LookupDiscovery
находит новые сервисы поиска.
• Метод getRegistrar интерфейса DiscoveryEvent получает массив объектов ServiceRegist-
• Класс LooknpDiscovery вызывает метод discarded, когда серзнс поиска должен быть
отвергнут, поскольку он больше не доступен, или поскольку он больше не соответствует
набору групп, в которых заинтересован серзнс или клиент Jini.
• Сервис Jini состоит из нескольких компонентов, каждый из которых вносит свой вклад
в гибкость и переносимость архитектуры Jini. Посредник сервисе представляет собой
промежуточное звено между сервисом Jini и его клиентами. Посредник сервиса
взаимодействует с реализацией сервиса через внутренний интерфейс сервнса, который определяет
методы в реализации сервнса. Отдельное приложение обнаруживает сервисы поиска и
регистрирует сервис Jini, делая его доступным для клиентов Jini.
• Клиент Jini использует известные способы обнаружения сервиса поиска. Клиент Jini ва-
тем использует обнаруженный сервис поиска для нахождения сервиса Jini. Когда сервис
поиска находит сервис, запрошенный клиентом, он сериалиаует посредник сервиса и
доставляет посредник клиенту Jini. Клиент затем может вызывать методы, определенные
в открытом интерфейсе сервнса, непосредственно на посреднике сервиса, который
реализует этот интерфейс. Сервис посредника взаимодействует с реализацией сервиса через
внутренний интерфейс.
• Интерфейс Entry (пакет net.jini.веге.епtry) описывает сервис, который позволяет
клиентам Jini искать сервисы с определенным описанием.
• Сервису поиска требуется класс Servioeltom (пакет net .Jini. core, lookup) для регистрации
сервиса Jini.
• Вспомогательные утилиты Jini упрощают процесс разработки приложений Jini. Эти
вспомогательные утилиты предостаилиют возможности у правлен ив высокого уровня.
• Класс LookupLocatorDiscovery дает возможность сервису или клиенту Jini обнаруживать
сервисы поиска на нескольких иввестных хостах. Класс LookupLocatorWseovery
использует объекты DiscoveryEvent для уведомления сервиса или клиента Jini об
обнаруженных сервисах поиска.
• Класс LooknpDisoeveryMaitager предоставляет возможность гибкого обнаружение
сервиса поиска, давая возможность приложениям и клиентам Jini выполнять обнаружение
сервиса поиска как с помощью однонаправленного, так и группового вещвння, используя
один и тот же клаос.
140 Глава 3
• Атрибуты Entry задают характеристики сервисов Jini. Присоединяя атрибуты к
сервисам, поставщики сервисов могут публиковать сервисы с подробной информацией о них,
включая местонахождение сервиса и функциональные возможности сервиса.
Разработчики также могут создавать собственные атрибуты для сервисов Jini. Класс AbstractEntry
предоставляет базовую реализацию интерфейсе Entry.
• Класс Entry должен предоставлять конструктор баз параметров. Переменные
экземпляров должны представлять собой ссылки типа public на сериалнзуемые (Serializable) об-ь-
■ Одно нз назначений технологии Jini — сделать сообщество Jini «свмовосстанавливаю-
вдимся» и дать возможность восстановления при воэнкяновении таких проблем, как
аварии в сети, аппаратные сбои и программные сбои. В соответствии с таким подходом, когда
сервис Jini регистрируетса сервисом поиска, регистрация не является постоянной.
Регистрация предостакаяется на определенный период времени, после чего сервис поиска
аннулирует регистрацию. Тем самым проблемные сервисы не могут парализовать
функционирование всего сообщества Java.
• Стратегия аренды, применяемая Jini, является достаточно жесткой, — если сервис Jini
не возобновляет свою аренду, сервис поиска прекращает регистрацию по истечении срока
аренды, делал сервис недоступным дня клиентов.
• Класс LeaseRenewalManager — вспомогательный класс Jini, который дает всоможность
сервисам управлять своей арендой, чтобы аренда сервиса не Была преждевременно
прекращена.
• Класс JoinManager — вспомогательный класс, который упрощает процесс
развертывания сервиса Jini, выполняя действия по обнаружению сервиса поиска, регистрации
сервиса и управлению арендой в одном классе.
• Сложные клиенты Jini часто предъявляют специфические требования к применяемым
ими сервисам Jini. Чтобы удокаетворить этим требованиям, клиент Jini часто должен
работать с группами сервисов Jini. Клиент осуществляет поиск среди сервисов, чтобы найти
определенный сервис, который может удовлетворить потребностям клиента. Класс Ser-
viceDiscoveryManager облегчает обнаружение имеющихся сервисов и позволяет
клиентам осуществлять избирательный поиск, что невозможно при использовании интерфейса
Servi ceRegistrar.
• Имеется три основных действия, в которых клиенты Jini используют класс Service-
Disco very Man age г: создалие локавьного кэша сервисов, получение уведомления о
событии, когда сервисы становятся доступными или недоступными, и выполнение
детализированного поиска, невозможного при использовании простого шаблона поиска Service-
Template.
■ Класс Servi ce Disco very Manager может повысить производительность клиента Jini путем
создания локального кэша обнаруженных оервисов. Этот локальный кэш реализуется как
объект LooknpCacbe.
Терминология
AbstractEntry, класс
createLooknpCacbe, метод класса
S ervice Disco very Man age г
discovery — обнаружение
Discovery Event, класс
Discovery Listener, интерфейс
DiscoveryManagement, класс
Entry, интерфрйп
get From, метод клаееа
Look upDiseoveryManager
get Groups, метод класса Lookup Discovery
getHost. метод класса LooknpLocator
get Port, метод клаоса LBokupLeeator
getRegigtrar, метод класса LookupDiecovery
group discovery — групповое обнаружение
Jini
Jini client — клиент Jini
Jini Software Kit
Jini Technology Core Platform Compatibility
Kit
Jini Technology Starter Kit
Jini transaction manager service — сервис
менеджера транзакций Jini
jini: URL
join, протокол
Join Manager, класс
Lease, класс
lease renewal service — сервис
возобновления аренды
Lease.FOREVER, константа
LeaseListener, интерфейс
LeaseRenewalManager, класс
Jini
141
locator discovery — локаторнсе обнаруже
lookup, метод класса
Service Disco veryM anager
lookup, метод клаоса ServiceDiscovery
lookup, метод интерфейса LooknpCacne
lookup service — сервнс поиска
LookupCache, интерфейс
LookapDisoevery, класс
Lookup Dleco very Manager, класс
LeokupLocator, клаос
LooknpLeeator Disco very, класс
multicaet discovery — обнаружение с пои
щью группового вещания
Name, класс
plug and play — «подключил и работай^
Reggie lookup Bervice — сервис поиска
Reggie
renewFer, метод класса LeaeeRenewaJ Manager
Упражнения для самоконтроля
3.1, Заполните пропуски в каждом из следующих высказываний:
а) Навовите три обязательных сервиса для выполнения сервисов и клиентов Jini:
b) Имеется два способа обнаружения сервисов поиска: и .
c) Чтобы сформировать файл-заглушку для удавенного объекта, следует использовать
утилиту .
d) Посредник сервиса, который загружается уделенным клиентом, должен реализовы-
вать интерфейс ■
e) Поставщики сервиса используют класс дня описания сервиса. Клиенты
Jini используют клаос для нахождения соответствующего сервиса.
3.2. Ответьте, является ли каждое из следующих высказываний истинным или ложным.
Если высказывание ложно, объясните, почему.
a) Обнаружение с помощью однонапраавенного вещания также известно кан локатор-
ное обнаружение.
b) Класс JoinManager может обнаруживать сервисы поиска, регистрировать сервис
и возобновлять аренду сервиса.
c) Класс LookupDiscoveryManager может выполнять только обнаружение с помощью
однонаправленного вещания.
d) Для функционирования Jim требуются только демон активации RMI (rmid) и Web-
сервер.
e) Для работы клиентов Jini требуется, чтобы переменная окружения CLASSPATH
содержала все файлы .clase.
Ответы на упражнения для самоконтроля
3.1. а) Web-сервер, демон активации rmi, сервис поиска. Ь) обнаружение с помощью
однонаправленного вещания, обнаружеияе с помощью группового вещания, с) nnic.
d) Serialixable. e) Service I tern, ServiceTemplate.
3.2. а) Иствино. Ъ) Истинно, с) Ложно. Класс LookapDieco very Manager выполняет
обнаружение как с помощью однонаправленного вещания, так и обнаружение с помощью
группового вещвния. d) Ложно. Дли Jini также требуется сервис поиска, дающий
возможность клиентам находить сервисы Jini. e) Ложно. Для функционирования
клиентов Jini требуется, чтобы в локальную переменную окружения CLASSPATH
включались только открытый интерфейс и классы поддержки. Бели поместить в переменную
CLASSPATH файлы .class сервиса, сетевая загрузка классов будет невозможна.
renew Until, метод класса
Lease Rene walM an age г
serviceAdded, метод класса
Service Disco ve ryLietene г
ServiceDisco very Listener, интерфейс
ServiceDiscoveryManager, класс
ServicelD, класс
serviceШNotify, метод интерфейса
ServioelD L istener
Service Item, класс
ServiceltemFilter, интерфейс
Service Registrar, интерфейс
serviBeRemoved, метод интерфейса
Se rvice D iscove ryListener
ServiceTemplate, класс
unicast discovery — обнаружение с
помощью одно направленного вещания
142 Глава 3
Упражнения
3.3. Модифицируйте иласс MultlcaetDiecovery (рис. 3.13), чтобы выполнить групповоз
обнаружение сервисов поиска, которые поддерживают любые группы, а не только группу
public. Можно ли использовать null в шаблоне для имени сервисе?
3.4. Создайте приложение для нахождения всех сервисов, которые зарегистрированы
локальными сервисами поиска.
3.5. На пишите с использованием технологии Jini сервис расчета обменных курсов валют.
Этот сервис должен выполнять всего одну функцию: осуществлять перевод валюты
одной страны в валюту другой страны. Курс обмена может динамически загружаться из
онлайнового ресурса или же статически загружаться на файла. Создайте открытый
интерфейс, посредник сервисе, внутренний интерфейс и реализацию сервиса.
3.6. Зарегистрируйте оервис расчета обменных курсов валют в сервисах поиска иа
локальной машине с использовали ем утилиты JolnManager.
3.7. Создайте клиент Jinl, который дает возможность пользователю применить сервис
расчета обменных курсов валют. Осуществите поиск сервиса расчета курсов обыска валют
с помощью сервиса поиска на локвльной машине и воспользуйтесь найденным оервн-
сом для пересчета одной валюты в другую.
3.8. Модифицируйте упражнение 3.6, добавив в сервис атрибуты Entry. Атрибуты должны
содержать яшмдш сервисе расчета курсов обмена валют, адрес сервисе расчета
курсов обмена валют и другую информацию, которую вы хотели бы добавить. Используйте
некоторые или воз атрибуты, чтобы найти соответствующий сервис.
Литература
Edwards, W.K. Core Jini (Second Edition), Uppar Saddle River, NJ: Prentice Hall, Inc.,
2001.
Li, S. Professional Jlni, Birmln«hain. U.K.: Wrox Press Ltd., 2000.
Newmarch, J., A Programmer's Guide to Jini Technology, New York, NY: Springer-Verlag
New York, Inc., 2000.
Oaks, S., BHd Wong, H. Jinl in a Nalahelt Sebaatopol, CA: O'Relly 4 Associates, Inc.,
2000.
JavaSpaces
Цели
• Научиться использовать
JavaSpaces для построения
распределенных приложений.
■ Получить представление об
операциях, доступных
в JavaSpaces.
• Научиться находить
в JavaSpaces записи,
соответствующие шаблонам.
• Понять, как использовать
транзакции в JavaSpaces.
• Научиться использовать
уведомления для построения
приложений JavaSpaces,
управляемых по событиям.
Мир — это книга,
те, кто не путешествуют,
читают только одну страницу.
Святой Августин
Пиши, что хочешь; никаких
других правил не существует.
О.Генри
Не суди по внешнему виду —
руководствуйся фактами.
Вот самое лучшее правило.
Чарльз Диккенс
Не верь ничему.
Не важно, прочел ли ты об этом,
или кто-либо об этом рассказал.
То, что я сказал,
не имеет значения, пока это
не будет воспринято твоим
разумом и здравым смыслом.
Будда
144 Глава 4
4.1. Введение
Объекты, которые являются частью распределенных систем, должны иметь воз
можность взаимодействовать друг с другом и совместно использовать информацию.
Мы уже познакомились с механизмами, с помощью которых объекты Java могут
взаимодействовать между собой. Например, RMI (глава 2) дает возможность
объектам Java, выполняющимся на рвзных виртуальных машинах, вызывать методы
друг друга, как если бы эти объекты находились на одном и том же компьютере.
Сервис JavaSpaces представляет собой сервис Jini, который реализует простую
высокоуровневую архитектуру для построения распределенных систем. Сервис
JavaSpaces даст возможность объектам Java взаимодействовать, совместно
использовать объекты и координировать задачи с помощью совместно используемой
области памяти [1]. Сервис JavaSpaces предоставляет три основные операции: запись
(write), изъятие (take) и чтение (read). Операция записи помещает объект — он
называется записью — в сервис JavaSpaces. Операция изъятия определяет шаблон
и удаляет из сервиса JavaSpaces запись, которая соответствует заданному
шаблону. Операция чтения схожа с операцией изъятия, но не удаляет соответствующую
JavaSpaces
145
шаблону запись из сервиса JavaSpaces. В дополнение к трем базовым операциям
сервисы JavaSpaces поддерживают транзакции, осуществляемые с помощью
менеджера транзакций Jinl, а также механизм оповещения, который уведомляет
объект, когда запись, соответствующая данному шаблону, записывается в сервис
JavaSpaces.
В первой половине этой главы мы представим основные принципы технологии
JavaSpaces и на простых примерах продемонстрируем операции, транзакции
и уведомления. В практическом примере в конце этой главы сервисы JavaSpaces
используются для построения распределенного приложения для обработки
изображений. Это приложение использует сервисы JavaSpaces для распределения работы
по применению фильтров к изображениям между несколькими программами
(обычно функционирующими на раиличных компьютерах).
4.2. Свойства сервиса JavaSpaces
Технология JavaSpaces облегчает проектирование и разработку
распределенных систем. Сервис JavaSpaces имеет пять основных свойств 12);
1. Сервис JavaSpaces является сервисом Jini.
2. Несколько процессов могут одновременно иметь доступ к сервису Java-
Spaces.
3. Запись, хранящаяся в сервисе JavaSpaces, будет оставаться там до
истечения срока аренды сервиса или до тех пор, пока программа не извлечет
запись из сервиса JavaSpaces.
4. Сервис JavaSpaces находит объекты, сопоставляя эти объекты с шаблоном.
Шаблон задает критерий поиска, на соответствие которому сервис Java-
Spaces проверяет каждую запись. Если одна или несколько записей
соответствуют шаблону, сервис JavaSpaces возвращает единственную
отвечающую шаблону запись.
5. Сервисы JavaSpacas используют менеджер транзакций Jini для
обеспечения выполнения последовательности операций.
6. Объекты в сервисах JavaSpaces используются совместно. Программы могут
считывать и извлекать записи из сервиса JavaSpaces, модифицировать
общедоступные записи и записывать их обратно в сервис JavaSpaces для
использования их другой программой.
4.3. Сервис JavaSpaces
Сервис JavaSpaces предоставляет распределенное, совместно используемое
хранилище для объектов Java. Любой совместимый с Java клиент может поместить
совместно используемые объекты в хранилище. Однако к этим объектам Java
предъявляется несколько требований. Во-первых, любой объект, хранящийся
в сервисе JavaSpaces, должен реализовывать интерфейс Entry (пакет net
.jini.core.entry). Записи (объекты Entry) сервиса JavaSpaces должны соответствовать
контракту Entry Jini, определенному в спецификации Jini Core Specification (см.
главу 3). Запись может иметь несколько конструкторов и столько методов,
сколько необходимо. Другими требованиями авляются наличие открытого
конструктора без параметров, общедоступных полей и отсутствие полей с примитивными
типами данных. Посредник сервиса JavaSpaces использует конструктор без
параметров для создания отвечающей шаблону записи Entry в процессе десериализации.
Все поля, которые будут использоваться в качестве шаблонов в записи Entry,
146
Глава 4
должны быть открытыми (подробнее о нолях шаблонов рассказывается в
разделе 4.8). Согласно определению, записанному в спецификации Jini Core
Specification, запись не может иметь полей с примитивными типами данных. Это
требование к полям объекта упрощает модель для шаблона, поскольку примитивные типы
не могут иметь значения null, которые используются в шаблонах в качестве
групповых символов (wildcards).
Для технологии JavaSpaces, так же как и для Jini, необходимо несколько
базовых сервисов. Сервис JavaSpaces яаляется зависимым от сервиса поиска Jini
(более подробная информация о сервисах Jini содержится в главе 3). Если требуется
провести транзакцию, должен быть запущен сервис транзакций Jini (раздел
4.11.2). Сервисы JavaSpaces также являются зависимыми от Web-сервера и rmid
(подробнее о запуске этих сервисов рассклзывается в главе 3). В раздале 4.6
разъясняются взаимоотношения между сервисами JavaSpaces и этими сервисами Jini.
В разделе 4.11 демонстрируется использование сервиса транзакций с сервисом
JavaSpecea. Чтобы воспользоваться сервисом JavaSpaces, нужно вапустить
утилиту outrigger, которая представляет собой реализацию сервиса JavaSpaces
корпорации Sun. Имеются две версии сервиса JavaSpaces: временный сервис JavaSpaces
(не активируемый) и постоянный сервис JavaSpaces (активируемый). Временный
сервис JavaSpaces не требует демона активации (rmid), поскольку он не является
активируемым. После завершения работы временного сервиса вся информация
о состоянии теряется, и демон активации rmid не способен запустить сервис.
Постоянный сервис JavaSpaces является активируемым, поэтому он требует демона
активации RMI. Если постоянный сервис JavaSpaces завершает работу, вся его
информация о состоянии сохраняется в файле журнала, и rmid может перезапустить
сервис позднее.
Чтобы запустить временный сервис JavaSpaces, введите следующую команду
в приглашении командной строки:
Java -Djava.security.policy=политика
-□Java. mi. aarrai, codebaeaa
http://хост:лорт/outriggar-dl.jar
-jar c:\fllaB\jinil_l\lib\tranaient-outrigger.jar public
где политика — путь к соответствующему файлу политики, хост — имя
компьютера, на котором выполняется Web-сервер, а порт — номе'р порта, ил котором
Web-сервер будет принимать соединения. Параметр public задает, к какой группе
принадлежит этот сервис.
Следующая команда запускает постоянный сервис JavaSpaces:
Java -jar c:\filM\jinil_l\lib\outriggar.jar
http://хост:аорт/outrigger-dl.jar
политика log path public
где хост — компьютер, на котором выполняется Web-сервар, порт — номер
порта, на котором Web-сервер будет принимать соединения, политика — полный
путь к файлу политики, a log_patn — место, где outrigger будет создавать журнал.
В системах, где действуют два или более сервиса JavaSpaces, будет полезен до-
полннтальный параметр:
-Dcom.sun.jini.outrigger.ярасаН*ме=имя
где имя представляет собой строку, с помощью которой сервис JavaSpaces будет
регистрировать себя в сервисе поиска Jini. Именем по умолчанию для сервиса
JavaSpaces является "JavaSpace". При поиске определенного сервиса JavaSpaces
сервисом поиска Jini нужно использовать объект Name Entry (пакет net.jlnl.look-
пр.entry) и инициализировать его строкой, указано в предыдущем параметре. В то
время как этот параметр является необходимым для систем с двумя или более сер-
JavaSpaces
147
висами JavaSpaces, в примерах в этой главе под разуме кается, что в системе
существует только один сервис JavaSpacea. Примеры знакомят с простым способом,
с помощью которого клиенты могут находить сервис JavaSpaces с помощью
сервиса поиска Jini.
Вы можете запустить как временный, так и постоянный сервис JavaSpaces с
помощью утилиты StartService, входящей а дистрибутив Jini. Запустите утилиту,
затем перейдите на вкладку TransientSpace (для временного сервиса JavaSpaces)
или FrontEndSpace (для постоянного сервиса JavaSpaces). Перейдите к вкладке
Ran, а затем щелкните на кнопке Start TransientSpace, чтобы вапустить
временный сервис, или щелкните на кнопке Start FrontEnd Space, чтобы запустить
постоянный сервис.
4.4. Обнаружение сервиса JavaSpaces
При ниидиалиэации каждый сервис JavaSpacea регистрирует себя с помощью
локального сервиса поиска Jini. Из главы 3 вы уже знаете, как запустить Web-сервер
и демон активации RMI. Ниже приведен пример команды, которая запускает
постоянный сервис JavaSpacea. Замените хост на имя или IP-адрес вашего компьютера,
а порт — ка номер порта, пе котором Web-сервер осуществляет прослушивание.
Java -jar C:\files\jinil_l\lib\outrigger.jar
http://хост-nopT/outriqgnr-41.jar
C:\fileB\jinil_l\policy\policy.all
C:\tmp\outrigger_log public
Класс JavaSрасeFinder (рис. 4.1) показывает, как получить доступ к сервису
JavaSpaces (мы используем этот класс в примере на рис. 4.3). Приложение
выполняет обнаружение для нахождения сервиса поиска Jini на хосте, уклзаином
пользователем. В строках 35-36 осуществляется получение объекта LookupLocator
для заданного пользователем URL Jini, а также получение для него регистратора
ServiceRegistrar. В строках 55-68 осуществляется поиск всех сервисов Java-
Spaces, зарегистрированных в сервисе поиска. В строках 55-57 задается объект
ServiceTemplate (пакет net.jini.core.lookap). В строках 61—62 объект Service-
Template используется для поиска всех соответствующих шаблону сервисов в
сервисе поиска и получения объекта Java Space. Более подробную информацию о том,
как использовать сервис поиска Jini, вы можете найти в главе 8.
1 // JavaSpaceFinder.Java
2 // Это приложение осуществляет одяокаправлевяое обнаружение
// сервиса JavaSpacea.
3 package com.deitsl.advjhtpl.javaapace.common;
4
5 // Основные пакеты Jini
6 import net.jini.core.discovery.LookupLocator;
7 import net. jini.core.lookup.1*;
8 ianxsrt net.jini.core.entry.Entry;
9
10 // Пакеты расширения Jini
11 import net.jini.apace.JavaSpace;
12
13 // Набор бааовшс шкетов Java
14 import java.io.*;
15 import Java.mi.*;
16 import java.net.*;
17
IB // ]
19 import javea.swing.*;
20
21 public class JavaSpacaFinder {
22
23 private JavaSpaca «pace,-
24
25 public JavaSpaceFinder( String jiniURL )
26 {
27 LookupLocator locator = null;
28 ServiceRegistrar registrar = null;
29
30 System.setSecurityManager( new RMXSecurltyHanager() );
31
32 // ищем сервис по адресу "jini://hostname",
33 // используя умалчиваемый номер порта и регистратор
34 try (
35 locator = naw LookupLocator ( jiniURL );
36 registrar = lonator.getHegiatrar();
37 )
38
39 // обработка исключения иа-эа некорректного URL
40 catch ( MalformedORLException malformedURLException )(
41 malformedURLException.printStackTrace();
// обработка исключения ввода/вывода
catch ( java.io.TOException ioExceptioi
ioException.printStackTrace();
// обработка исключения при помехе класса
catch ( ClaaaHotFoundBxcaption elassNotFoundException
claaaHotFoimdBxception.printStackTrace() ,-
)
// указание требований к сервису
С1аяв[] type» = new Class[l { JavaSpaca.class 1;
ServiceTenplate template =
new ServiceTempiate( null, types, null );
// помех сервиса
try (
( JavaSpaca ) registrar.lookup( template );
)
// обработка исключения при получении JavaSpaoes
catch ( RemoteException remot«Exception ) (
remoteException.printStackTraceO ;
// не найдено ни одного сервиса
if ( space = null ) {
System.out.printLn( "No matching servioe"
75 } // завершение конструктора JavaSpaceFindar
76
77 public JavaSpace gatJavaSpace()
78 {
79 return apace;
80 }
81 }
Рис. 4.1. Обнаружение сервиса JavaSpaces
4.5. Интерфейс JavaSpace
Клиенты осуществляют доступ к сервису JavaSpaces через интерфейс Java-
Space (пакет net.jini.space). Интерфейс JavaSpace предоставляет несколько
методов: notify, read, readlfExists, take, takelfExists, write и snapshot. Назначение
этих методов следующее [2]:
1. write — данный метод реализует операцию записи write. Операция write
помещает запись (объект Entry) в сервис JavaSpaces. Если идентичная запись
уже существует в сервисе JavaSpaces, эта операция не заменяет
существующую запись. Вместо этого в сервис JavaSpaces помещается копия записи.
Сервисы JavaSpaces могут содержать множество копий одной и той же
записи. В разделе 4.7 демонстрируется, как использовать операцию write.
2. read, readlfExists — эти два метода реализуют операцию чтения read,
которая пытается прочесть из сервиса JavaSpaces запись (объект Entry),
соответствующую шаблону. Если соответствующей шаблону записи в сервисе
JavaSpaces нет, операция возвращает null. Если в сервисе JavaSpaces
имеется несколько записей, соответствующих шаблону, операция read
произвольно выбирает одну из них. Метод read блокируется до тех пор, пока
в сервисе JavaSpaces не будет найдена запись, соответствующая шаблону,
или пока не наступит тайм-аут. Метод readlfExists проверяет, существует
ли в сервисе JavaSpaces соответствующая шаблону запись. Если запись не
существует, метод readlfExists должен сразу же возвратить null.
Выполнение метода readlfExists не блокируется, если только соответствующая
шаблону запись не участвует в незавершенной транзакции. О транзакциях речь
пойдет в разделе 4.11. Примеры использования операций read и
readlfExists демонстрируются в разделе 4.8.1.
3. take, takelfExists — эти два метода реализуют операцию изъятия take,
которая пытается удалить из сервиса JavaSpaces запись, соответствующую
шаблону. Эта операция работает так же, как операция read, за исключением
того, что при этом она удаляет соответствующую шаблону запись из сервиса
JavaSpaces. Метод take блокируется до тех пор, пока в сервисе JavaSpaces не
будет найдена соответствующая шаблону запись, либо пока не наступит
тайм-аут. Метод takelfExists проверяет, существует ли в сервисе JavaSpaces
запись, соответствующая шаблону. Если запись не существует, метод
takelfExists немедленно возвращает null. Выполнение метода takelfExists не
блокируется, если только соответствующая шаблону запись не является частью
незавершенной транзакции. Применение операций take и takelfExists
демонстрируется в разделе 4.8.2.
4. notify — этот метод реализует операцию уведомления notify, которая
предписывает сервису JavaSpaces отправить извещение объекту-слушателю,
когда клиент записывает соответствующую шаблону запись в сервис JavaSpa-
150
Глава 4
сез. Этот метод дает возможность приложению избежать постоянных
проверок наличия записи в сервисе JavaSpaces. Использование операции
notify демонстрируется в разделе 4.9.
5. snapshot — этот метод повышает эффективность, когда программе
требуется многократно осуществлять сериализацию записи. При каждой передаче
программой объекта Entry в сервис JavaSpaces (например, при ее записи
или использовании в качестве шаблона) этот объект должен быть сериали-
зозан. Если программа передает одну и ту же запись сервису JavaSpaces
многократно, процесс сериализации может занять достаточно много
времени. При вызове метода snapshot объект Entry сериаянзуется один раз, этот
се риал изо ванный объект Entry многократно используется в будущих
передачах. В разделе 4.10 демонстрируется, как использовать метод snapshot.
4.6. Определение записи
В следующих разделах и подразделах будет создано приложение для
управления регистрацией воображаемых семинаров, проводимых компанией Deitel &
Associates, Inc. Для каждого семинара средство администрирования записывает
объект AttendeeConnter (рис. 4.2) в сервис JavaSpaces. Каждый объект Attendee-
Counter хранит число участников, зарегистрированных для определенного
семинара. Мы поэтапно реализуем приложение, чтобы продемонстрировать
использование каждой из операций сервиса JavaSpaces.
1 // AttendeeCountar.Java
2 // Определяем AttandeeCounter — объект типа Kntry.
3 package cont.deitel.advjhtpl. javaspace.comon;
*
5 import net.jini.core.entry.Entry;
6
7 public class Attend—Countar implements Entry {
в
9 public String day;
10 public Integer counter = new Integer( 0 };
11
12 // конструктор б*а параметров
13 public AttandeeCounter(> ()
14
15 // конструктор с •дикстмшом строковым параметром
16 public AttendeeCounter( String eemicarDay }
17 (
IS day = setinarDay;
19 }
20 } .
Рис. 4.2. AttendeeConnter - объект типа Entry для отслеживания регистрации
участников семинара, проводимого в определенный день
Объект AttendeeConnter (рис. 4.2) представляет собой объект Entry, который
содержит число участников семинара. Напомним, что записи должны содержать
типы данных, не являющиеся примитивными, общедоступные поля (строки 9—10}
и пустой конструктор (строка 13). Конструктор AttendeeConnter в строках 16-19
принимает в качестве строкового параметра день недели, для которого этот объект
AttendeeConnter отслеживает регистрацию участников.
JavaSpaces 151
б4п Типичная ошибка программирования 4.1
(jjQJ Включение в запись полей примитивных типов данных не вызывает
ошибки во время компиляции. Однако при сериализации возбуждается
исключение lUegatArgumentException.
S Общая методическая рекомендация 4.1
Вместо примитивных типов данных используйте в полях записи Entry
кл ассы-оберт ки.
4.7. Операция записи
Операция записи помещает объект Entry в сервис JavaSpaces. Метод write
принимает три параметра: объект Entry, объект Transaction и значение типа long,
которое задает время, в течение которого сервис JavaSpaces должен хранить объект
записи Entry. Значение iong представляет собой продолжительность аренды для
записи. Обычно сервис JavaSpaces предоставляет каждому записанному объекту
Entry время аренды, равное 5 минутам. Сервис JavaSpaces ве будет хранить объект
Entry свыше предоставленного срока аренды. Разработчик может увеличить время
жизни записи Entry, возобновив действие аренды до ее истечения. Метод write
возвращает объект net.jini.lease. Lease, который содержит время, на которое
сервис JavaSpaces предоставляет доступ к записи. Метод write возбуждает два
исключения. Исключение RemoteException (пакет java.rrai) возбуждается либо при
возникновении сбоя в сети, либо в случае возникновения некоторых других ошибок
на сервере. Если операция write имеет место в некорректной транзакции, метод
write возбуждает исключение TranaactionException (пакет net-Jlni.core.trans-
aetion). Подробнее о транзакциях рассказывается в разделе 4.11.
Приложение WriteOperation (рис. 4.3) использует класс AttendeeConnter
(рис. 4.2) и класс JavaSpaceFinder (рис. 4.1} для демонстрации записи объекта
Entry в сервис JavaSpaces. В этом примере администратор семинара использует
приложение WriteOperation для помещения записей AttendeeConnter для
каждого имеющегося в сервисе JavaSpaces семинара. Конструктор (строки 30-36)
принимает в качестве параметра объект JavaSpaee. Метод writeEntry (строки 39-62)
записывает объект Entry в сервис JavaSpaces. В строках 44-45 инициализируется
объект Entry, устанавливая число зарегистрированных участников семинара
разным нулю. В строке 46 осуществляется запись объекта Entry в сервис JavaSpaces.
Второй параметр (nnii) указывает, что операция write не использует транзакцию
(объект Transaction). После завершения операции записи объект записи Entry
готов для выполнения над ним операций чтения read н изъятия teke. Бели объект
Transaction указан, операция write использует его, чтобы обеспечить успешное
завершение операций транзакции. Это подразумевает, что до успешного завершения
транзакции другие клиенты не могут читать или изымать запись из сервиса
JavaSpaces. Третий параметр (Leaee.FOREVER) задает, как долго сервис Java-
Spaces будет хранить запись (объект Entry). Хотя нам желательно, чтобы сервис
JavaSpaces хранил нашу запись бесконечно долго, в реализации Sun время аренды
ограничено 5 минутами. Программы могут использовать механизм возобновления
аренды Jini для установки аренды (Lease) для записей (Entry). После истечения
аренды сервис JavaSpaces удаляет и уничтожает объект.
Метод showOotput (строки 65—75) отображает результаты. В методе main
в строках S1-85 проверяется заданное пользователем имя хоста. В строках 38-90
пользователю предлагается выбрать определенный день для записи. На рис. 4.4
показаны результаты выполнения приложения WriteOperation.
152
Глава 4
1 // WriteOperation.Java
2 // Данное приложение инициализирует нодой объект Entry
3 // и закосит его в JavaSpaees.
4 package com.deitel.advjhtpl.javaapace.Mrite;
5
6 // Базовые пакеты Jini
7 import net.jini.core.lease.Lease;
8 import net.jini.core.traBsaction.TransactionException;
9
10 // Пакеты расширений Jini
11 import net.jini.space.JavaSpace;
12
13 // Бааовнй паке* Java
14 import java.rmi.RemoteException;
15
16 // Пакет расширений Java
17 import javax.swing.*;
18
19 // Пакет Deitel
20 import com.deitel.advjhtpl.javaspace.common.*;
21
22 public class WriteOperation (
23
24 private JavaSpace space;
25 private static final String[] days = ( "Monday", "Tuesday",
26 "Wednesday", "Thursday", "Friday" );
27 private String output = "\n";
28
29 // конструктор WriteOperation
30 public WriteOperation( String hostname )
31 (
32 // получение JavaSpace
33 String jiniORL = "jini;//" + hostname;
34 JavaSpaceFinder findtool ■ new JavaSpaceFinder( jiniORL );
35 space = findtool.getJavaSpace();
36 >
37
38 // помещение объекта Entry в JavaSpaees
39 public void writeEntryl String day )
40 (
41 // иницкалкаация объекта Att endeeConnter типа Entry
42 // и его помещение в JavaSpace*
43 try (
44 AttendeeCounteг counter = nev AttendeeCounter( day );
45 space.write( counter, null, Lease.FOREVER );
46
47 output +e "initialize the Entry; \n";
48 output += " Day: " + day + "\n";
49 output += " Count: 0\n";
50 )
51
52 // обработка исключения при сбое в сети
53 catch ( ReeeteException exception } (
54 exception.printStackTrace();
55 >
56
JavaSpaces
57 // обработка исключения ма-*а некорректной транзакции
53 catch ( TransactionException exception } (
59 exception.printStackTraceO;
60 )
«1 >
62
63 // отображение вывода
64 public void showOutput()
65 (
66 JTextArea outputArea = new JTextAreaO;
67 outputArea.setText( output );
68 JOptionPane.BhowMessageDialogf null, outputArea,
69 "Writ«Operation Output",
70 JOptionEane.INFOBMATI0S_MESSAGE );
71
72 // завершение программы
73 System.exit( 0 );
74 )
75
76 // метод main
77 public static void main( String args(] )
78 (
79 // получение имени хоста
80 if ( args.length •= 1 ) (
81 System.out.println(
82 "Usage: Writ «Operation hostname" ) ,-
83 System.exit( 1 } ;
84 )
85
86 // получение ввода пользователи (дня недели}
87 String day = ( String ) JOptionPana.showInputDialog(
88 null, "Select Day", "Day Selection",
89 JOptionPane.QOESTlON_MESSAGE, null, days, days( 0 ] );
90
91 // аалменваем объект Entry
92 WriteOperation write = new WriteOperation( args( 0 ) );
93 write.writeEntry( day );
94
95 write.showOutputО;
96
97 ) // завершение метода main
98 }
Рис 4.3. Запись обьекта Entry в сервис JavaSpaces
Рис. 4.4. Результаты выполнения приложения WriteOperation
Глава 4
Это приложение принимает параметр командной строки, который задает имя
хоста, выполняющего сервис JavaSpaces. Для компиляции и выполнения
приложения WriteOperation необходимы следующие действия. Убедитесь, что
переменная окружения CLASS PATH включает в себя пути к файлам jini-core.jar, jini-
ext.jar и sun-util.jar. Откомпилируйте файлы Java в каталоге com\deitel\
advjhtpl\javaspace\common. Выполните приложение WriteOperation, указав
имя хоста с сервисом поиска Jini. He забудьте указать для JVM файл политики
с соответствующими полномочиями.
4.8. Операции чтения и изъятия
Операции чтения read и изъятия take извлекают записи Entry из сервиса
JavaSpaces. Клиент может прочесть или изъять запись из сервиса JavaSpaces,
предоставив шаблон записи, с которым будут сравниваться общедоступные поля
записей в сервисе JavaSpaces. Шаблон указывает, какие поля использовать для целей
сравнения.
В процессе извлечения применяется механизм сопоставления С шаблоном для
нахождения записей, имеющих соответствующие значения в их общедоступных
полях. Поля типа public в каждой записи в сервисе JavaSpaces должны
представлять собой ссылки на объекты, либо имея значение noil, либо указывая на объект.
Поля в шаблоне, значения которых не равны noil, должны в точности совпадать со
своими (двойниками* в записях сервиса JavaSpaces. Поля в шаблоне, которые
имеют значения null, действуют в качестве групповых символов. Если в сервисе
JavaSpaces имеется группа записей одного типа, для нахождения соответствующей
записи или набора записей, содержащихся в сервисе JavaSpaces, используются
только те поля, которые совпадают с полями шаблона. Для полей шаблона,
имеющих значения null, значения соответствующих полей в записях в сервисе Java-
Spaces могут быть любыми.
4.8.1. Операция чтения
Операция чтении read получает записи, не удаляя их из сервиса JavaSpaces.
Методы read и readlfExists выполняют операцию чтения. Каждый метод
принимает три параметра: объект Entry, который задает шаблон, объект Transaction
и значение типа long. Значение типа long имеет различный смысл для методов
read и readlfExiste. Для метода read оно задает период времени, в течение
которого операция чтения будет продолжать блокироваться, прежде чем возвратит null.
Метод readlfExists, в отличие от метода read, не блокируется. Если
соответствующих шаблону записей нет, метод readlfExists сразу же возвращает null. Метод
readlfExists блокируется только тогда, когда разработчик указел период времени
ожидания и соответствующая шаблону запись является частью незавершенной
травзакции. Бели соответствующая шаблону запись не участвует в какой-либо
транзакции, то операция read возвращает ату запись немедленно. И метод read,
и метод readlfExists возвращают только одну валясь. Если шаблону соответствуют
несколько записей, операция чтения произвольно извлекает одну из них. Эти
методы возбуждают четыре типа исключений: RemoteExceptlon, TransactionExcep-
tion, UnusableEutryException и InterruptedException. Первые два исключения
такие же, как и в методе write. Если соответствующая шаблону запись не может
быть десериалиэована, эти методы возбуждают исключение UnnsableEntryExcep-
tion (паяет net.Jini,cere.ontry).
Приложение ReadOperation (рис. 4.5) использует класс AttendeeCounter (рис. 4.2)
и класс JavaSpaceFinder (рис. 4.1) для демонстрации чтения записи из сервиса
JavaSpaces
155
JavaSpaces. Администратор семинара или потенциальный участник может
воспользоваться приложением, чтобы познакомиться со списком зарегистрированных
участников определенного семннара. В строке 47 задается шаблон соответствия, с
которым будут сравниваться записи. Пользователи должны указать день, для которого
они хотели бы посмотреть список зарегистрированных участников семинара.
1 // ReadOperation.Java
2 // Это приложение читает объект Entry иэ JavaSpaces
3 // и отображает информацию иа него.
4 package com.deitel.advjhtpl.javaspace.read;
5
6 // Бажояна пакет» Jini
7 import net.jini.core.transaction.TransactionException;
8 import net.jini.core.entry.OnusableEntryZxception;
9
10 // Пакт*! расширений Jini
11 import net.jini.space.JavaSpace;
12
13 // Баеовне пакеты Java
14 import Java.rati.RenotaException;
15 import java.lang.InterruptedException;
16
17 // Пакеты расширений Java
18 import javax.swing.*;
19
20 // Пакета Deitel
21 import com.deitel.advjhtpl.javaapace.common.*;
22
23 public class ReadOperation (
24
25 private JavaSpace space;
26 private static final String[1 days = ( "Monday", "Tuesday",
27 "Wednesday", "Thursday", "Friday" );
28 private String output = "\n";
29
30 // конструктор
31 public ReadOperation ( String hostname )
32 1
33 // попучекие JavaSpaces
34 String jiniURL = "jini://" + hostaame;
35 JavaSpaceFinder findtool = new JavaSpaceFinder( jiniOHL );
36 space = f indtool. get JavaSpace () ,-
37 >
3B
39 // получение объекта Entry ка JavaSpaces
40 public void readEntry( String day )
41 {
42 // аадаиие наблока, чтение m JavaSpaces
43 // и отображение кифоркации и» объекта Entry
44 try (
45
46 // чтение Entry ка JavaSpaces
47 AttendeeCounter counter ■ new AttendeeCounter( day );
48 AttendeeCounter resultCounter = ( AttendeeCounter )
49 apace.read( counter, null, JavaSpace.HO_HAlT };
50
: (reaultCounter = null) (
output += "Sorry, cannot find an Entry for '
+ day + "!\n";
// получение информации ив объекта Entry
output +■ ''Count Information:\n";
output +» " Day: " + reaultCounter.day;
output += "\n";
output += " Count: "'
4- reaultCounter.counter.intValue() + "\n" ;
)
J
// обработка исключения ори сбое а сети
catch ( SemoteException exception ) (
exception.printStackTrace();
>
// обработка исключения ие-аа некорректной транзакции
catch ( TranaactionException exception ) (
exception.printStackTrace();
// обработка исключения из-за ншомониости десериалмаацми
catch ( ШшааЫвЕп try Except ion exception ) (
exception.printStackTrace(),-
// обработка исключения иэ-эа прерывания
catch ( InterruptedException exception ) (
exception.printStackTrace();
36 ) // завершение метода readEntry
87
8B // отображение вывода
89 public void *howOutput()
90 (
91 JTextArea outputArea = new JTextArea {);
92 outputArea.eetText( output );
93 JOptionPene.ehOHHessageDialog( null, outputArei
94 "ReadOperation Output",
95 JOptionPane.XHFORMATION_HESSAGB );
96
97 // завершение программы
98 System.exit( 0 );
99 )
100
101 // метод main
102 public static void main( String arg*[| )
103 (
104 // получение имени хоста
105 if ( arge.length != 1 ) (
106 System, oat.println(
107 "Osage: ReadOperation hostname" );
108 System.exit( 1 );
109 )
110
111 // получение дшя ста пользователя
112 String day = ( String ) JOptionPane. ehowInputDialog (
113 null, "Select Day", "Day Selection",
114 JOptionPane.Q0ESTION_HESSAGE, null, daya, daya [ 0 ] );
115
116 // чтение обтлк-га Entry
117 ReadOperation read = new ReadOperation( args( 0 ] );
118 read.readEntry( day } ,-
119
120 read.showOutputO ;
121
122 ) // аааершеяие метода main
123 )
Рис. 4.5. Чтение записи из сервиса Java Spaces
Первый параметр метода read (строки 48-49) задает запись (объект Entry),
которую будет использовать механизм сопоставления с шаблоном. Второй параметр
(noil) указывает, что эта операция чтения не использует транзакцию. Третий
параметр (JavaSpace.NOWAIT) задает период, в течение которого операция чтения
ожидает, пока будет найдена соответствующая шаблону запись, прежде чем
возвратить null в случае, если таких записей не обнаружено. В нашем примере для
метода read установлено время ожидания JavaSpace.NO_WAIT, что эквивалентно
нулю. Если механизм сопоставления с шаблоном не находит соответствующую
шаблону запись, операция чтения read сразу же возвращает null. На рис. 4.6
показаны результаты выполнения приложения ReadOperation.
Это приложение принимает параметр командной строки, который оодает имя
хоста, на котором выполняется сервис JavaSpaces. Для компиляции и выполнения
приложения ReadOperation необходимы следующие действия. Убедитесь, что ваша
переменная окружения CLASSPATH включает файлы jmi-core.jar, jmi-ext.jar
и sun-util.jar. Откомпилируйте файлы Java в каталоге com\deitel\advjhtpl\
javaspace\read. Запустите приложение ReadOperation, указав имя хоста сервиса
поиска Jini. Не забудьте указать виртуальной машние Java файл политики с
соответствующими полномочиями.
Рис. 4.6. Результаты выполнения приложения ReadOperation
рь—, Общая методическая рекомендация 4.2
Операция read возвращает только одну соответствующую шаблону
запись. Если существует несколько соответствующих шаблону записей,
операция read может каждый раз возвращать другой соответствующий
шаблону объект.
158
Глава 4
4.8.2. Операция изъятия
Операция изъятия take получает запись (объект Entry) и удаляет ее из сервиса
JavaSpaces. Методы take и takelfExists выполняют операцию изъятия. Методы
take и takelfExists схожи с методами read и readlfExists. Единственное различие
состоит в том, что соответствующан шаблону запись, возвращаемая операциями
take или takelfExists, удаляется из сервиса JavaSpaces.
Приложение TakeOperation (рис. 4.7) использует класс AttendeeCoonter (рис. 4.2)
и класс Java Space Finder (рис. 4.1) для демонстрации изъятия записи из сервиса
JavaSpaces. Это приложение сходно с приложением ReadOperation. Бдииственаая
разница заключается в том, что это приложение вызывает метод take (строки
46-47} интерфейса JavaSpace для удаления объекта AttendeeCoonter из сервиса
JavaSpaces. Администратор семинаре может воспользоваться этим приложением,
чтобы удалить из сервиса JavaSpaces запись AttendeeCoonter для семинара,
который уже состоялся, либо для семинара, который был отменен. На рис. 4.8
показаны результаты выполнения приложения TakeOperation.
Это приложение принимает параметр командной строки, который задает имя
хоста, выполняющего сервис JavaSpaces. Для компиляции и выполнения
приложения TakeOperation необходимы следующие действия. Убедитесь, что
переменная окружения CLASS PATH включает пути к файлам jini-core.Jar, jmi-ext.jar
и sun-оШ.jar. Откомпилируйте файлы Java в каталоге com\deitel\advjhtpl\
javaspace\take. Запустите приложение TakeOperation, указав имя хоста сервиса
поиска Jini. He забудьте указать виртуальной машине JVM файл политики с
соответствующими полномочиями.
1 // TakeOperation.Java
2 // Это приложение удаляет объект Entry us JavaSpaces.
3 package com.deitel.advjhtpl.javaspace.take;
Л
5 // Базовые пакеты Jini
6 import net.jini.core.transaction.TransactionBxception;
7 import net.jini.core.entry.OnusableBntrySxception;
8
9 // Пакет расширений Jini
10 import net.jini:apace.JavaSpace;
11
12 // Базовый пакет Java
13 import java.rmi.RenoteXxception;
14
15 // Пакет расширений Java
16 import javax.swing.*;
17
18 // Паке* Deitel
19 import com.deitel.advjhtpl.javaspace.common.*;
20
21 public class TakeOperation (
22
23 private JavaSpace space ■ null;
24 private static final String[] days = ( "Monday", "Tuesday",
25 "Wednesday", "Thursday", "Friday" };
26 private String output = "\n";
27
28 // Конструктор для работы с JavaSpaces
29 public TakeOperation( String hostname )
30 f
// получение JavaSpacea
String jiniURL = "jini://" + hostname;
JavaSpaceFindar findtool = new JavaSpaceFindar( jiniURL );
space = findtool.getJavaSpace();
// удаление объекта Entry из JavaSpacea
public void TakeAnEntryf String day )
(
AttendeeCounter resultCounter = null;
// задание шаблона, удаление записи, соответствующей
// шаблону из JavaSpacea
try (
AttendeeCounter count = new AttendeeCounter( day ),-
resultCounter = ( AttendeeCounter ) space.take( count,
null, JavaSpace. Ж>_иА1Т );
if ( resultCounter = null) (
output +« "No Entry for " + day
+ " is available from the JavaSpace.\n";
)
else {
output += "Entry is taken away from ";
output += "the JavaSpace successfully.\n";
)
)
// обработка исключения при сбое в сети
catch ( Hamot«Exception exception ) (
exception.printStackTracef);
}
II обработка исключения при некорректной транзакции
< catch ( TransасtionExcaption exception ) (
exception.printstackTraca();
)
// обработка исключения, если объект Entry не может
catch ( DnusableEntryException exception ) (
exception.printStackTracef);
// обработка исключения при прерывании
catch ( intarruptedException exception ]
exception.printstackTrace();
)
) // завершение метода TakeAnEntry
// отображение вывода
public void showOutputf)
(
JTextArea outputArea = naw JTextArea();
outputArea.setText( output );
JOptionPane.shoMMeasageDialogf null,
"TakeOperation Output",
JOptionPane.INF0BHATIO»_HESSASE );
// а «вершение программ»
System.exit( 0 );
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115 >
public static void main( String arge[] )
(
// получение имени хоста
if ( args.length != 1 ) (
System.out.println(
"Usage: WriteOperation hostname!'
System.exit( 1 );
)
// получение пользовательского ввода
String day * ( String ) JOptionPans.showXnputDialog{
null, "Select Day", "Day Selection",
JOptionPane.QUESTION_HESSASB, null, day*, days[ 0 ] );
// получение otaemi Entry
TakeOperation take = пен TakeOperation( args[ 0 ] );
take.ТакеЛпЕпtry{ day );
take.showOutput();
} II завершение метода main
Рис. 4.7. Изъятие записи из сервиса JavaSpaces
Рис. 4.8. Результаты выполнения приложения TakeOperation
рнн--! Общая методическая рекомендация 4.3
> Операция изъятия возвращает только одну запись, соответствующую
шаблону. Если существует несколько записей, отвечающих шаблону,
операция изъятия может удалить из сервиса JavaSpaces только одну запись
за раз. Чтобы изъять из сервиса JavaSpaces все соответствующие
шаблону записи, многократно выполните приложение TakeOperation до тех
пор, пока оно не возвратит сообщение «No Entry is available from the
JavaSpaces service* («В сервисе JavaSpaces нет соответствующих
условию записей» ).
JavaSpaces
161
4.9. Операция уведомления
Операция notify предписывает сервису JavaSpaces посылать уведомления
слушателю, когда клиент записывает соответствующий шаблону объект Entry в
сервис JavaSpaces. Метод notify принимает пять параметров: объект Entry, задающий
шаблон, объект Transaction, слушатель, реализующий интерфейс RemoteEvent-
Listener (пакет net.jini.core.event), значение типа long, которое задает
длительность аренды для регистрации слушателя, и объект Marshal led Object (пакет
java.rmi), который сервис JavaSpaces будет передавать удаленному слушателю как
часть уведомления. Этот метод может возбуждать исключения типа RemoteEx-
ception и TransactionException. Исключение RemoteException возникает при сбое
в сети. Исключение TransactionException возникает, когда операция уведомления
имеет место при некорректной транзакции.
Класс EntryListener (рис. 4.9) определяет слушатель, который сервис Java-
Spaces будет уведомлять при записи в сервис объекта Entry, отвечающего
заданному шаблону. Слушатель EntryListener прослушивает сервис JavaSpaces на
предмет записи отвечающего шаблону объекта Entry в сервис. Этот слушатель должен
реализовывать интерфейс Remote Event Listener (строка 14). Человек,
заинтересованный принять участие в семинаре в конкретный день, может воспользоваться
этим приложением, чтобы получить уведомление при добавлении объекта Atten-
deeCounter для семинара, проводимого в определенный день. Конструктор
принимает один параметр — объект RemoteEvent Listener — и экспортирует слушатель
в сервис JavaSpaces, чтобы при записи клиентом в сервис JavaSpaces записи,
соответствующей шаблону, сервис посылал бы уведомление (вызывая метод notify).
Метод notify (строки 35-40) пересылает уведомление приложению Notify-
Operation (рис. 4.10).
1 // EntryListener.Java
111 Этот масс определяет слушатель для
3 // приложения NotifyOperation.
4 package com.deitel.advjhtpl.javaspaee.notify;
5
6 // Базовые пакеты Jini
7 import net.j ini.core.event.*;
8
9 // Базовые пакеты Java
10 import java.rmi.RemoteException;
11 import java.rmi.server.OnicastRemoteObject;
12 import java.io.Serializable;
13
14 public class EntryListener implements RemoteEventListener (
15
16 private RemoteEventLietener eventListener;
17
18 // Конструктор EntryListener
19 public EntryListener( RemoteEventListener listener )
20 (
21 eventListener = listener;
22
23 // экспорт объекта-заглушки
24 try {
25 DnicastRemoteObject.exportObject( this );
26 }
27
28 // обработка исключения при экспорте заглушки
6 Заж 204
162
Глава 4
29 catch ( RemoteException remoteExcept!on ) (
30 remot«Exception.printStackTrace();
31 )
32 J
33
34 // получение уведомления
35 public void notify( RenoteEvent remoteEvent )
36 throws UnknotmEventException, RemoteException
37 (
38 // передача подтверждения
39 eventListenor.notify( remoteEvent );
40 )
41 >
Рис. 4.9. Слушатель ErrtryListener для приложения NotifyOperatlon
Приложение NotifyOperation (рис. 4.10) демонстрирует, как написать
программу, которая получает уведомление, когда соответствующий шаблону объект Entry
записывается в сервис JavaSpaces. В строках 30-36 определяется конструктор,
который получает сервис JavaSpaces. В методе notifyEntry (строки 39-61) в строке 42
осуществляется получение слушателя EntryListener, который прослушивает сервис
JavaSpaces на наличие записей, соответствующих шаблону. Этот слушатель Entry-
Listener будет передан методу notify интерфейса JavaSpace. В строке 48 создается
шаблон. В строках 50-51 определяется объект, который будет посылаться
слушателю при выдаче уведомления. В строках 52-53 вызывается метод notify интерфейса
JavaSpace. Первый параметр (counter) задает запись (объект Entry), которая
используется в качестве шаблона. Второй параметр (null) указывает, что операция
уведомления не выполняется в процессе транзакции. Третий параметр (listener)
представляет собой экземпляр класса EntryListener. Четвертый параметр (600000)
задает запрашиваемое время аренды в миллисекундах. По истечении срока аренды
слушатель выходит из активного состояния. Последний параметр (handback —
ссылка на объект MarshalledObjcct) представляет собой объект, который сервис
JavaSpaces предоставляет удаленному слушателю при уведомлении.
1 // NotifyOperation.Java
2 // Это приложение получает уведомление при записи объекта
3 // Entry, соответствующего шаблону.
4 package com.deitel.advjhtpl.javaspace.aotify;
5
6 // Базовие пакеты Java
7 import net.jini.core.transaction.TransactionException;
8 import net.jini.core.lease.Lease;
9 import net.jini.core.event.*;
10
11 // Пакеты расширении Jini
12 import net.jini.space.JavaSpace;
13
14 // Базовый пакет Java
15 import java.rmi .*;
16
17 // Пакет стамдарткых расширений Java
IB import javax.awing.*;
19
20 // Пакет Deitel
21 import com.deitel.advjhtpl.javaspace.common.*;
22
23 public class NotifyOperation implements RenoteEventLiateпег
24 (
25 private JavaSpace space;
26 private static final String[] days = ( "Monday", "Tuesday",
27 "Wednesday", "Thursday", "Friday" );
28
29 // Конструктор
30 public NotifyOperationf String hostname )
31 (
32 // получение JavaSpace
33 String jiniURL = "jini://" + hoatnama;
34 JavaSpaceFinder findtool * new JavaSpaceFinder( jiniURL );
35 space » findtool.gatJavaSpace();
36 )
37
38 // вызов метода notify
39 public void notifyEntry( String day )
10 (
EntryListener listener = пет EntryListener( this );
// определение шаблона, который отправляет*
// уведомления при записи объекта Entry,
// cooTaevcTBynqei-o шаблону
try (
Attendee-Counter counter = new AttendeaCounter ( day ) ;
HarshalladDbject handback = new MarshalledObjectf
"JavaSpace Notification" ) ,-
apace.notify!
counter, null, listener, 10 * 60 * 1000, handback );
)
// обработка исключения при уведомлении
catch ( Exception exception ) (
exception.printStackTracef);
)
) // аалерпгекиа метода notify En try
// отображение ныаода
public void showOutput( String output )
(
JTextArea outputArea = new JTextArea();
outputArea.setTextf output );
JOptionPane.ahowHeasageDialog{ null, outputArea,
"NotifyOperation Output",
JOptionPane.INTORMATI08_HESSAGE );
)
// получение уведомления
public void notify( RemoteEvent remoteEvent )
(
String output = "\n";
// подготовка вывода
try (
output += "id: " + remoteEvent.getID() + "\n";
output += "sequence number: "
-I- remoteEvent.getSequenceNuitiberO + "\n";
Stxing handback = ( String )
remoteEvent.getRegistrationObjectO.get();
output += ''handback: " + handback + "\n";
// отображение вывода
showOutput ( output ) ;
)
// обработка исключения при получении объекта handback
catch ( Exception exception ) {
exception.printstackTrace();
)
97 // метод main
98 public static void main( String acgs[] )
99 {
100 // получение имени хоста
101 if ( args.length != 1 ) (
102 System.out.printlnj
103 "Osage: NotifyOperation hostname" );
104 System.exit( 1 );
105 )
106
107 // получение данных от пользователя
108 String day = ( String ) JOptionPane.shovInputDialog(
109 null, "Select Day", "Day Selection",
110 JOptionPane.QUESTION_HBSSAGE, null, days, daysl 0 ] );
111
112 // уведомление
113 NotifyOperation notifyOperation =
114 new NotifyOperation( args[ 0 ] );
115
116 notifyOperation.notifyEntry ( day ),-
117
118 } II завершение метода main
119 )
Рис. 4.10. Получение уведомлений при записи соответствующих шаблону объектов
Entry в сервис JavaSpaces
Для выполнения приложения NotifyOperation необходимы следующие
действия. Убедитесь, что переменная окружения CLASSPATH содержит пути к файлам
jini-core.jar, jLni-ext.jar и son-u til. jar. Откомпилируйте исходные файлы в
каталоге com\deitel\advjhtpl\javaspace\notify. Запустите Web-сервер. Сформируйте
заглушку для класса EntryListener (рис. 4.9). Создайте JAR-файл для класса
Entry Listen er_Stub.class и поместите его в каталог документов Web-сервера.
Выполните приложение NotifyOperation, указав имя хоста сервиса поиска Jini. He
забудьте указать виртуальной машине Java (JVM) базу кодов и файл политики
с соответствующими полномочиями.
iavaSpaces
165
На рис. 4.11 показаны примеры работы приложения. Чтобы протестировать
приложение, выполните его несколько раз
Рис. 4.11. Примеры работы приложений NotttyOperation
Ш Общая методическая рекомендация 4.4
Доставка уведомлений сервиса JavaSpaces не гарантируется.
Нормальной доставке уведомлений могут помешать проблемы, возникшие в сети.
4.10. Метод snapshot
Метод snapshot оптимизирует взаимодействия с сервисом JavaSpaces,
уменьшая затраты, связанные с многократной сериализацией записей (объектов Entry).
Каждый раз, когда мы передаем шаблон методам в интерфейсе JavaSpace,
необходимо осуществить сервализацию объекта Entry перед передачей его сервису
JavaSpaces. При неоднократной передаче одного и того же объекта сервису
JavaSpaces желательно избежать многократной сериал и задии этого объекта.
Метод snapshot принимает шаблон и возвращает особое его представление (момен
/пильный снимок объекта Entry). Этот моментальный снимок может быть
использован только в том сервисе JavaSpaces, который его создал. Например, чтобы
удалить из сервиса JavaSpaces все записи, относящиеся к семинару, проводимому
в понедельник, мы вызываем метод snapshot, чтобы создать моментальный
снимок записи, а затем многократно передаем этот моментальный снимок записи
методу take.
Приложение SnapshotUsage (рис. 4.12) удаляет объекты Entry из сервиса
JavaSpaces и использует метод snapshot, чтобы избежать многократной сериализа-
ции шаблона. В строке 49 определяется шаблон. Мы не передаем этот шаблон
методу take. Вместо этого мы передаем методу take моментальный снимок. В строке
50 вызывается метод snapshot, чтобы получить моментальный снимок шаблона.
1 // SnapshotOsage.Java
2 // Это приложение удаляет записи на JavaSpaces
3 // с помощь» метода snapshot.
4 package com.deital.advjhtpl.javaspace.snapshot;
5
6 // Базовые пакет» Jini core
7 import net.jini.core.transaction.TransactionException;
8 import net.jini.core.entry.OnusablaEntryException;
9 import net.jini.core.entry.Entry;
10
11 // Пакет расширений Jini
12 import net.jini.space.JavaSpace;
13
14 // Базовый пакет Java
IS Import java.rnu..RemotaException;
16
П II Пакет расширения Java
18 import javan.awing.*;
19
20 // Пакет Deitel
21 Import com.deitel.advjhtpl.javaspace.common. *;
22
23 public class SnapshotOsage {
24
25 private JavaSpace space;
26 private static final String[] days = ( "Monday", "Tuesday",
27 "Wednesday", "Thursday", "Friday" );
28 private String output = "\n";
29
30 // конструктор
31 public SnspshotD'sage( String hostname )
32 К
33 // получоиме JavaSpaceз
34 String jiniURL = "jinir//" + hostname;
35 JavaSpacerinder findtool = new JavaSpaceFinder( jiniORL );
36 space » Cindtool.getJavaSpace{);
37 )
38
39 // создаем мгновенный снимок обчекаа,
40 // передаем его ш качестве параметра методу take
41 public void snapshotEntry( String day )
42 (
43 // задание шаблона, создание нгноаемлого снимка,
44 // удаление записей, соответстаукввпс шаблону, из
45 // JavaSpaceа с помощь» игновешвого снимка
46 try (
47 XttandeeCounter counter ■» new AttendeeCounter( day );
48 Entry snapshotantry * space.snapshot( count»г );
49 AttandeeCountar rasultCounter * ( AtteadeeCountar )
50 space.take( anapshotentry, null, JavaSpace.HO_HAlT );
51
52 // удаление всех записей
53 //
54 while ( resultCountar \= null ) (
55 output += "Removing an entry ... \n";
56 resultCountar = (XttendeeCountar) apace.take(
57 anapshotentry, null, JavaSpace.NO WAIT );
58 }
II обработка исключения ма-за сбоя в сети
catch ( RemoteException reeoteExcaption ) \
remoteException.printStackTrace{);
// обработка исключения мз-за некорректной транзакции
catch ( TransactionException transactionException ) {
transactionException.printStackTrace();
71 i
72
73 // обработка исключен** ка-эа некорректного объекта Entry
74 catcb { UnuaableEntryException unusableEntryExcaption ) {
75 unueableEntryException.printStackTraceO ;
76 }
77
70 // «ели аоэбукяается исклпчвкие
79 catch ( IntarruptedException interruptedExcaption } {
80 interruptedException.printStacJcTrace() ;
81 )
82
83 ) // конец метода snapahotEntry
84
85 // отображен»!* выходных данных
86 public void ehowOutputO
87 (
88 JTextArea outputAraa = new JTextAraa();
89 outputAraa.setTextf output );
90 JOptionPane.showMeeaagebialog( null, outputAraa,
91 "SnapahotDeaga Output",
92 JOptionPane.INFORMATION MESSAGE };
93
94 // заверяете программы
95 System.exit( 0 ) ;
96 )
97
9B // метод main
99 public static void main( String argaf,] )
100 (
101 // получение имени хоста
102 if ( arga.length != 1 ) {
103 System.out.println(
104 "Daage: SnapahotUaage hoatnane" );
105 Syatem.exitf 1 );
106 )
107
108 // получение nf°°" о* пользователя
109 String day ■= { String ) JOptionPane.ahowlnputDialog(
110 null, "Select Day", "Day Selection",
111 JOptionPane.QOESTION_HESSAGE, null, daya, daya[ 0 ] );
112
113 // моментальный снимок объекта Entry
114 SnapshotUeage anapehot = new SnapahotOaage( arga[ 0 ] );
115 anapehot.anapahotEntxy( day );
116
117 anapehot.ahowOutputf);
118
119 ) // завершение метода main
120 }
Рис. 4,12. Удаление записей из сервиса JavaSpaces с помощью метода snapshot
Единственный параметр метода snapshot (строка 48) задает шаблон,
подлежащий сериализации. Метод snapshot возвращает моментальный снимок записи,
которая представляет шаблон. В строках 49-51 вызывается метод take, чтобы уда-
168
Глава 4
лить запись (объект Entry), которая соответствует шаблону, из сервиса JavaSpaces.
На рис. 4.13 показаны результаты выполнения приложения SnapshotUsage. Из
них видно, что в сервисе JavaSpaces имеются три соответствующие шаблону
записи. Операция изъятия удаляет все три записи из сервиса JavaSpaces.
Рис. 4,13. Окно с результатами выполнений приложения SnapshotUsage
Для компиляции и выполнения приложения SnapshotUsage необходимы
следующие действия. Убедитесь, что переменная окружения CLASSPATH включает
файлы jini-core.jar, jini-ext.jar и sun-util.jar. Откомпилируйте файлы Java в
каталоге com\deitel\advjhtpl\javaspace\snapshot. Выполните приложение Snapshot-
Usage, указав имя хоста сервиса поиска Jini. He забудьте указать виртуальной
машине Java файл политики с соответствующими полномочиями.
S Общая методическая рекомендация 4.5
Использование моментального снимка записи эквивалентно
использованию оригинальной записи, если все операции выполняются в том же самом
сервисе JavaSpaces. который сгенерировал моментальный снимок.
4.11. Обновление записей с помощью
сервиса транзакций Jini
Мы не можем напрямую модифицировать запись в сервисе JavaSpaces. Вместо
этого нам потребуется изъять запись (объект Entry) из сервисе JavaSpaces,
изменить значения полей записи, а затем снова поместить запись в сервис JavaSpaces.
Чтобы сервис JavaSpaces не потерял запись в процессе ее изъятия, мы можем
выполнить операции изъятия, обновления и записи в составе транзакции. Если все
операции выполнены успешно, транзакция фиксируется. В противном случае
транзакция оказывается неудачной, и сервис JavaSpaces совершает откат —
возвращает запись в состояние до начала транзакции.
Вообразим распределенную систему, в которой выделенные узлы изымают
записи из сервиса JavaSpaces, обрабатывают, а затем помещают их (с помощью
операции write) обратно в сервис JavaSpaces. Что происходит, если возникает
проблема, и один из выделенных узлов не возвращает обработанную запись?
Информация, которую обрабатывал узел, может оказаться навсегда потерянной. Кроме
того, поскольку обрабатывающий узел удалил запись из сервиса JavaSpaces,
необработанная запись также теряется. Применение менеджера транзакций позволяет
защитить сервис JavaSpaces от подобных ситуаций. При неудаче менеджер
транзакций восстанавливает запись в ее исходном состоянии, как если бы клиент
никогда не обращался к записи.
Наш следующий пример демонстрирует, как обновить запись я сервисе Java-
Spaces. Приложение принимает запись AttendeeConnter из сервиса JavaSpaces,
обновляет переменную счетчика и сноза вставляет запись в сервис JavaSpaces. Адми-
нистратор семинара может воспользоваться этим приложением, чтобы
зарегистрировать нового участника семинара и обновить соответствующий объект
AttendeeCoanter. Нам нужно гарантировать, что если клиент изъял запись из
сервиса JavaSpaces, она будет записана в сервис JavaSpacea позднее. В этом примере
мы используем менеджер транзакций Jini, который гарантирует, что только один
клиент одновременно может обновить запись для семинара. 6 противном случае
клиент может переписать ранее созданную запись, что исказит правильный учет
людей, которые хотели бы принять участие в семинаре.
4.11.1. Определение пользовательского интерфейса
В этом разделе будет определен пользовательский интерфейс для приложения.
Чтобы обновить запись, программа должна знать, какой объект AttendeeCoanter
обновлять, какое число прибавлять к значению счетчика. Класс Updatelnpat-
Window (рис, 4.14) запрашивает у пользователя день недели, для которого следует
выполнить обновление, а также число людей, которые будут участвовать в
семинаре, проводимом в этот день.
1 // OpdatelnputHindow.Java
2 // Данное приложение предоставляет интерфейс
3 // для ввода данных.
4 package com.deitel .advjhtpl. javaspace.update,-
5
6 // Пакеты растирания Java
7 import javax.swing.*;
8
9// Базовые пакеты Java
10 import java.atrt. *;
11 import java.awt.event.*;
12
13 public class UpdateInputwindow extends JFrame {
14
15 private String[] dates = ( "Monday", "Tuesday",
16 "Wednesday™, "Thursday", "Friday" };
17 private JButton okButton;
IS private JComboBox dateComboBox;
19 private jLabel firstLabel,-
20 private JTextField numberText;
21 private String date = "Monday";
22 private int count = 0;
23 private String hostname;
24
25 public UpdateInputWindov( String name )
26 (
27 super ( "UpdateInputwindow" ) ;
28 Container container = getContentPane();
// определение центральной панели
JPanel center Panel = new JPanelQ,-
centerPanel.setLayout( new GridLayout(2,2,0,5) );
"Please choose a date:"
38 SvingConstant*.CENTER >;
39 centerPanel.add( firatLabel );
40
41 // добавление списка
42 dataComboBox = new JCoaboBox( date* );
43 det«ConboBox.MtSelectedlndex( 0 );
44 centerPanel.add( dateComboBox );
45
46 // добавление слушателя к списку
47 dateCoaboBox.addItemListener(
48
49 new ItenLi*tener() (
50
51 public void itemStateCnanged( ItemEvent itemJEvent )
52 (
53 date - ( String ) dateCcaboBox.getSelectedItem()■
54 )
55 )
56 ),-
57
58 // добавление надписи
56 JLabel nunberLabel = new JLabel(
60 "Please specify a number:", SwingConatanta.CUltTER );
61 centerPanel.add( numberLabel );
62
63 // добавление текстового поля для мода деикых
64 nunberText * new JTextFiald( 10 );
65 centerPanel.add( nunberText );
66
67 // добаалеяме слушателя для текстового поля
68 nunberText.addActionLiatener(
69
70 new ActionLiatener() {
71
72
73
74
75
76
77 )
78 );
79
80 // определение панели с каопками
81 JPanel buttonPanel = new JFanal();
82 buttonPanel.setLayout( new GridLayout( 1, 1, 0, 5 ) );
83
84 // добавление кнопки OK
85 okButton я sew JButton( "OK" ) ,-
86 buttonPanel.add( okButton );
87
88 // добавление слушателя для кнопки OK
8 9 okButton.addActionLiatener(
90
91 new ActionLiatener () (
92
93 public void actionPerfomed( ActionEvent event )
public void actionPerforaed( ActionEvent event )
(
count = Integer.peraelntf
event.getActionCoe«and() );
)
95 // полутени* ввода пользователя
96 count = Integer.paraelnt( numbexText.getText() );
97
98 if ( count = 0) (
99 System■out.println(
100 "Please Specify a Nunber" );
101 )
102
103 else (
104 OpdateOperation update = new OpdateOperation();
105 String jiniURL - "jini://" + hostname;
106 update.getServices( jiniURL );
107 update -updateEntry( date, count );
108
109 aetVi«ible( false );
110 update.showOutput();
111 )
112 )
113 )
114 ) :
115
116 // собираем все вместе
117 container.add( centerPanel, BorderLayout.CENTER );
118 container.add( buttonPanel, BorderLayout.SOUTH );
119
120 // задание размеров оава и его отображение
121 setSize( 320, 130 ) ;
122 setViaible( true ) ;
123
124 ) // завершение конструктора updateInputWindow constructor
125 1
Рис. 4.14. Пользовательский интерфейс Updateln put Window
4.11.2. Обнаружение сервиса TransactionManager
Для создания транзакции необходим менеджер транзакций. 6 нашем примере
мы используем сервис Jini TransactionManager для получения менеджера
транзакций. Предполагается, что из главы 3 вы уже знаете, как запустить Web-сервер
и демон активации RMI.
-Doom.sun.jini.mahalo.managerNane*TransactionManager
с:\files\jinil_l\lil>\mahalo.jar
http://имя-хоста:порт/mahalo-dl.jar
c:\files\jinil_l\policy\poliey.all
c:\mahalo\txn_log public
Класс TransactionManagerFinder (рис. 4.15) демонстрирует сервис
TransactionManager. Это приложение выполняет однонаправленное обнаружение для
нахождения сервиса поиска Jini, с его помощью мы можем получить ссылку на сервис
TransactionMamager. Приложение ищет сервис TransactionManager в сервисе
поиска. В строках 40-43 задается объект ServiceTemplate, который в строках 46-47
используется для нахождения сервиса поиска. Метод get TransactionManager
(строки 72-75) возвращает менеджер транзакций TransactionManager.
1 // TransactionManagerFinder.^ava
2 // Это приложение осуществляет обнаружение
3 // сервиса TransactionManager.
4 package com.deitel.advjhtpl.javaapace.common;
5
6 // Основные пакеты Jini
7 import net.jini.core.discovery.LookupLocator;
8 import net.jini.core.lookup.*;
9 import net.jini.core.entry.Entry;
10 import net.jini.core.transaction.server.TransactionManager;
11
12 // Базовые пакеты Java
13 import java.io.*;
14 import java.rmi.RMISecurityManager;
15 import java.net.*;
16
17 // Пакеты расширений Java
18 import javax.swing.*;
19
20 public class TransactionManagerFinder (
21
22 private TransactionManager transactionManager = null;
23
24 public TranaactionManagerFinder( String jiniURL )
25 (
26 LookupLocator locator = null;
27 Servicettegistrar registrar = null;
28
29 System.satSecurityManager( new RMXSecurityManager() );
30
31 // полупить сервис поиска по адресу
32 // "jini://имя-хоста", используя порт по умолчании
33 try (
34 locator = new LookupLocator( jiniURL );
35
36 // регистрация
37 registrar = locator.getRegistrar();
38
39 // требования x сервису
40 Class[1 types = new Claas[] (
41 TransactionManager.claaa );
42 ServiceTemplate template —
43 new ServiceTemplate( null, types, null );
44
45 // поиск сервиса
46 tranaactionManager =
47 <TransactionManager) registrar.lookup( template );
48 }
49
50 // обработка исключения при некорректном URL Jini
51 catch ( MalfoxmedORLExcaption malfonaedURLException ) {
52 malformedOKLException.printStackTraceO,-
53 ,
54
55 // обработка исключений ввода/вывода
56 catch ( lOExcaption ioException ) (
JavaSpaces
173
57 ioException.printStackTrace();
58 )
59
60 // обработка исключений при поиске апассов
61 catch ( ClassNotFoundExceptioji claeoNotFoundException ) {
62 claeeNotFoundException.printStackTrace();
63 )
64
65 //не найдена служба
66 if ( transactionManager = null ) (
67 System.out.println( "No matching service" );
68 )
69
70 ) // конец конструктора TransactionManagerFinder
71
72 public TransactionManager getTransactionManacer()
73 (
74 return transactionManager;
75 )
76 } __
Рис. 4.15. Поиск менеджера транзакций TransactionManager Jini
4.11.3. Обновление записи
Теперь у нас есть интерфейс пользователя (класс UpdatelnputWindow),
менеджер транзакций (класс TransactionManagerFinder) и сервис JavaSpaces (класс
JavaSpaceFinder), готовые к применению. Следующий шаг — собрать все
воедино, чтобы построить приложение для обновления записей AttendeeCounter.
Приложение UpdateOperation (рис. 4.16) обновляет записи посредством
транзакций. Метод main формирует интерфейс Updatelnput Window, дающий
возможность пользователю выбирать день и вводить новое значение для числа участников
AttendeeCounter. Метод getServices (строки 40-50) получает ссылку на сервис
JavaSpaces.
Метод updateEntry (строки 53-136) обновляет запись AttendeeCounter в
контексте транзакции. 6 строках 67-68 с помощью передачи методу create
интерфейса TransactionFactory создается транзакция менеджера транзакций и
длительности аренды. Метод create возвращает объект Transaction.Created. 6 строках 69-70
задается длительность аренды для транзакции. В строке 85 создается шаблон
AttendeeCounter, которому будут соответствовать записи AttendeeCounter
сервиса JavaSpaces. 6 строках 86-87 запись изымается из сервиса JavaSpaces с
помощью метода transactionCreated-transaction. Бели операция UpdateOperation
успешно извлекает запись AttendeeCounter, то она модифицирует эту запись с
учетом информации о дне проведения семинара и числе его участников (строки
98-102) и записывает ее обратно в сервис JavaSpaces. Бели операция
UpdateOperation не находит записей AttendeeCounter, она ничего не предпринимает.
6 строках 115-116 осуществляется фиксация транзакции, завершая ее и
заканчивая аренду. Если в какой-либо момент возникает исключение, в строках 126-127
транзакция прерывается, кроме того завершается аренда.
1 // UpdateOperation.Java
2 // Это приложение удаляет запись и» JavaSpaces,
3 // изменяет значения полей в записи
4 // и помещает обновленную запись в JavaSpaces.
5 // Все эти операции осуществляются ■ транзакции.
6 package com.deitel.advjhtpl.javaspace.update;
7
8// Базовые пакеты Jini
9 import net.jini.core.lease.Lease;
10 import net.jini.core.transaction.*;
11 import net.jini.core.entry.OnuaableEntryException;
12 import net.jini.core.transection.server.TransactionHanager;
13
14 // пакет» расширений Jini
15 import net.jini.space.JavaSpace;
IS import net.jini.lease.*;
17
18 // Валовые пакеты Java
19 import java.raii.RemoteException;
20
21 // Пакеты распираний Java
22 import javax.swing.*;
23
24 // Пакеты Deitel
25 import com.deitel.advjhtpl.javaspace.common.*;
2Б
27 public class OpdateOperation (
28
29 private JavaSpace space;
30 private TransactionHanager transactionManagar;
31 private static String hostnane = "";
32 private static String day = "**;
33 private static int inputCount «0;
34 private static String output = "\n";
35
36 // конструктор по умолчание
37 public OpdateOperationO {)
38
39 // конструктор получает JavaSpace» и TransactionManager
40 public void getServiees( String jiniURL )
41 (
42 // получение JavaSpaces и TransactionHanager
43 JavaSpaceFinder findtool =
44 пей JavaSpaceFinder( jiniURL );
45 apace ■ findtool.getJavaSpaca();
46 TransactionManagarFinder findTransection =
47 пей Transact!onManagerFinder( jiniURL );
48 transactionManager =
49 findTransaction.getTransactionManager();
50 }
51
52 // обновление «алией
53 public void updateEntry( String inputoay, int countNumber )
54 (
55 day = inputoay;
56 inputCount = countNumber;
57
58 AttendeeCounter resultCounter = null;
59 Transaction.Created transactionCreated ■ null;
60 LeaseRenewa1Manager manager = new LeaseRenawalManager();
62 int oldCount = 0;
63 int newCount = 0;
64
65 // создание транзакции и обновление аренды
66 try {
67 transасtionCreated = TranaactionFactory.create(
68 transactionManager, Lease.FOREVER );
69 manager.renewDntil<
70 transactionCreated.lease, Lease.FOREVER, null );
71 )
72 // обработка исключении при создании транзакции
73 //и обновлении аренды
74 catch ( Exception exception ) {
75 exception.printstackTrace();
76 }
77
76 // задание шаблона, удаление записей, соответствующих
79 // шаблону из JavaSpaces в транаахции, изменение
80 // значений переменных и запись обновлении
81 // л JavaSpaces в транзакции
82 try (
83
84 // удаление записи из JavaSpace
85 AttendeeCounter count ж new AttendeeCounter( day );
86 resultCounter = ( AttendeeCounter ) apace.take( count,
87 transactionCreated.transaction, JavaSpace.N0_HAIT ) ,-
88
89 // если записи отсутствуют
90 if ( resultCounter = null ) (
91
92 // выдача сообщения
93 output += " No matching Entry is available!\n";
94 )
95 else ( // запись найдена
96
97 // обновление значения
98 oldCount = resultCounter.counter.intValue();
99 newCount = oldCount + inputCount;
100
101 // помещение обновленной записи в JavaSpaces
102 resultCounter.counter = пей Integer( newCount );
103 space.write( resultCounter,
104 transactionCreated.transaction. Lease.FOREVER );
105
106 // вывод результата при успешном запзршении тралзакаин
107 output += "Count Information:\n";
108 output += " Day: ",-
109 output t= resultCounter.dey + "\n";
110 output +■= " Old Count: " + oldCount + "\n";
111 output += " Net» Count: " + newCount + "\n";
112 )
113
114 // фиксация транзакции и завершение аренды
115 transactionCreated.transaction.commit();
116 manager.remove( transactionCreated.leeae );
118 ) // конец try
1X9
120 // обработка исключения при обнов™
121 catch ( Exception exception ) (
122 exception.printStackTrace();
123
124 // откат и отказ от аранды
125 try (
126 tranaactionCreated.transaction.abort();
127 manager.remove( transactionCreated.laaaa );
128 )
129
130 // обработка исключения при откате
131 catch ( Exception abortException ) (
132 abortException.printStackTrace();
133 )
134 ,
135
136 ) // конец метода updateEntry
137
138 // вывод
139 public void »howOutput()
140 (
141 JTextArea outputArea = new JTextArea() ,-
142 outputArea.setText( output );
143 JOptionPana.showHessageDialog( null, outputArea,
144 "UpdateOperation Output",
145 JOptionPane.INFOBMATIOH_HESSAGE );
146
147 // завершение программы
148 System.exit( 0 );
149 )
150
151 public static void main( String агдаЦ)
152 1
153
154 if ( arga.length != 1 ) (
155 System.out.println{
156 "Usage: UpdateOperation hoatnana" );
157 System.exit( 1 );
158 }
159 else
160 hostname = args[ 0 ];
iei
162 // получение входа пользователя
163 UpdateiuputHindow input = new UpdateinputNindow( hosti
164
165 }
166 )
Рис. 4.16. Обновление записи с использованием менеджера транзакций TransactionManager
На рис. 4.17 и 4.18 представлены результаты выполнения приложения для
обновления записи. 6 окне WriteOperation Ontpnt на рис. 4.17 представлен итог
выполнения приложения WriteOperation. Мы инициализируем запись для среды
(Wednesday). Окно Updateln put Window на рис. 4.17 содержит интерфейс
пользователя, позволяющий ему/ей предоставить информацию, подлежащую
обновлению, такую как число участников семинара, проводимого в заданный день. В этом
окне мы указываем, что в семинаре, проводимом в среду, примут участие 15
человек. В окне UpdateOperation Output на рис. 4.18 показан результат выполнения
приложения UpdateOperation. 6 окне ReadOperation Output на рис. 4.18
представлен результат чтения записи за среду.
Рис. 4.17. Окно с результатами выполнен!
интерфейс UpdatelnputWindow
i Write О ре rati on и пользовательски
Рис. 4.18. Окна с результатами выполнения UpdateOperation и Read Operation
Чтобы выполнить это приложение, необходимо запустить Web-сервер, демон
активации HMI, сервис поиска Jini, сервис JavaSpaces и сервис Transaction-
Manager. Для компиляции и выполнения приложения UpdateOperation
необходимы следующие действия. Убедитесь, что переменная окружения CLASSPATH
содержит пути к файлам jini-core.jar, jini-ext.jar и sun-util.jar. Откомпилируйте
файлы Java в каталогах com\deitel\advjhtpl\javaspace\common и com\deitel\
advjhtpl\javaspace\update. Запустите приложение UpdateOperation, указав имя
хоста сервиса поиска Jini. He забудьте указать файл политики с соответствующи-
очиями.
4.12. Практический пример.
Распределенная обработка изображений
Обработка изображений, особенно больших, может потребовать значительных
затрат времени. В этом практическом примере мы используем сервисы JavaSpaces
для построения распределенной системы обработки изображений с применением
фильтров (размывания, выделения границ и т.д.). Мы определяем класс Image-
ProcessorCiient для разбивки изображения на небольшие фрагменты и записи этих
фрагментов в сервис JavaSpaces. Несколько классов ImageProcessor выполняются
параллельно, обрабатывая фрагменты изображения путем применения соответст-
вующих фильтров и последующей записи обработанных изображений обратно в
сервис JavaSpaces. Класс ImageProcessorClient после этого изымает обработанные
фрагменты изображения из сервиса JavaSpaces и собирает обработанное
изображение. Вазовая структура приложения ImageProceseor показана на рис. 4.19.
Обработав
объект XB*g«Entry
Необработанный
объект XmagaEntxy
Xmag»Proc«**orCli*nt
Запись (writ*)
необработанного
объекта
Рис. 4.19. Структура распределенного приложения ImageProcessor
4.12.1. Определение обработчика изображения
В этом практическом примере распределенная инфраструктура состоит из
набора выделенных узлов обработчиков изображений, которые извлекают записи из
сервиса JavaSpaces. Каждый из этих узлов обрабатывает запись и помещает ее
обратно в сервис JavaSpaces. Обработчики изображений имеют четыре фильтра:
размытия, изменения цветов, инверсии и выделения границ. Фильтр размытия
осуществляет размытие изображения. Фильтр изменения цветов изменяет
RGB-составляющие цвета в изображении. Фильтр инверсии обращает значения
RGB-составляющих цвета изображения. Фильтр выделения границ осуществляет
выделение границ областей изображения. Каждый узел обработки изображений
в этой распределенной системе постоянно обращается к сервису JavaSpaces за
записями, подлежащими обработке.
Приложения могут использовать распределенную систему для обработки
изображений. В нашем практическом примере таким приложением яиляется
ImageProcessorClient (рис. 4.28). Приложение ImageProcessorClient будет запрашивать
у пользователя имя файла и количество фрагментов, иа которое следует разбить
исходное изображение. Затем приложение ImageProceeeorClient записывает
фрагменты в сервис JavaSpaces. После этого приложение будет обращаться к сервису
JavaSpaces, пока не изияечет все обработанные фрагменты. Наконец, приложение
осуществит сборку изображения и отобразит результаты пользователю.
Класс ImageEntry (рис. 4.20) определяет записи (объекты Entry), которые при-
в может хранить в сервисе JavaSpaces. В строках 16-20 определяются от-
крытые поля записи. В строке 23 определяется обязательный пустой конструктор.
Строки 26-34 определяют конструктор, который инициализирует все поля в
записи ImageEntry. В строках 37—41 определяется конструктор, который
инициализирует поля name и processed. В строках 44-47 определяется конструктор, который
инициализирует поле name.
1 II ImageEntry.java
2 // Этот класс определяет объект Entry для изображения.
3 package com.deitel.advjhtpl.javaapace.ImageProcessor;
4
5 // Базовые пакет» Java
6 import Java,util.*-
7
8 // Стандартные расширения Java
9 import javax.swing,Imagelcon;
10
11 // Базовые пакета Jini
12 import net.jini.core.entry.Entry;
13
14 public class ImageEntry implements Entry {
15
16 public String name;
17 public String filter;
18 public Xnteger number;
19 public Boolean processed;
20 public imagelcon imagelcon;
21
22 // конструктор бее параметров
23 public ImageEntry О {)
24
25 // конструктор ImageEntry
2Б public ImageEntry( String imageName, String imageFilter,
27 int order, boolean done, imagelcon icon )
28 t
29 name = imageName;
30 filter = imageFilter;
31 number = nev Integer{ order );
32 processed •* new Boolean { done ) ;
33 imagelcon = icon;
34 )
35
36 // конструктор ImageEntry
37 public ImageEntry{ String imageName, boolean done )
38 t
39 name = imageName;
40 processed = new Boolean{ done );
41 )
42
43 // конструктор ImageEntry
44 public ImageEntry{ String imageName )
45 {
4 6 namft = imageNaott,*
Рис. 4.20. Класс ImageEntry определяет записи, которые будут сохранены е сервисе
JavaSpaces
Класс ImageProcessor (рис. 4.21) представляет в распределенной системе
обработки изображений узел, который способен обрабатывать изображения. Узлы
ImageProcessor обращаются к сервису JavaSpaces для получения записей Image-
Entry. Когда клиент помещает (методом write) необработанную запись ImageEntry
в сервис JavaSpaces, первый узел ImageProcessor, который извлечет эту запись,
и будет ее обрабатывать. Узел ImageProcessor создает транзакцию для каждой
записи, которую он извлек, и не фиксирует транзакцию, пока обработанная запись
ImageEntry не будет помещена обратно в сервис JavaSpaces. Таким образом, если
действия, предпринимаемые обработчиком ImageProcsssor, окончатся неудачей,
запись ImageEntry не будет потеряна.
1 // ImageProcessor.Java
2 // Это приложение уяедоилеех слушатель, когда объект
3 // ImageEntry, нуждашийся в обработке, зависая a JavaSpaces.
4 package com.deitel.advjhtpl.javaspace.lmageProcessor;
5
6 // Стандартен» расширения Java
7 import javax.swing.*;
S
• 9 // Базовые пакеты Jini core
10 import net.jil
11 import net.jil
12 import net.jini.core.transaction.server.TransactionHanager;
13 import net.jini.i
14 import net.jii
15 import net.jini.lease.*;
16
17 // Пакеты расширений Jini
18 import net.jini.space.JavaSpaca;
19
20 // Накати Deitel
21 import com.deitel.advjhtpl.javaspaee.coemon.*;
22
23 public class ImageProcessor {
24
25 private JavaSpace space;
26 private Transact!onManager manager,-
27
28 // Конструктор ImageProcessor
29 public ImageProcessor { String hostname )
30 {
31 // получение JavaSpaces
32 String jiniOKL « "jini://" + hostname;
33 JavaSpaceFinder finder =
34 new JavaSpaceFinder( jiniORL };
35 space = finder.getJavaSpace();
36
37 // получение менеджера транзакций
38 TransactionManagerFinder findTransaction =
39 new TransactionManagerFinder{ jiniORL ) -
40 manager =
41 findTransection.getTransactionHanager{};
42 )
43
44 // ожидают необработанного изобращения
45 public void waitForinageO
LeaseRenewalManager leaseManager =
пей LeaseRenewalManager{);
while ( true ) {
// получение и обработка изобрад
try <
Transaction Created transactionCreated =
TransactlOnFactory.create(
manager. Lease.FOREVER );
II обновление аренды
leaseManager.renewUntil(
transactionCreated.lease, Lease.FOREVER, null ) ;
ImageEntry template = new ImageEntry( null, false );
ImageEntry entry = { ImageEntry ) space.take(
template, transactionCreated.transaction.
Lease.FOREVER );
if ( entry != null ) t
// получение значка изображения
Imagelcon imagelcon = entry.imagelcon;
Filters filters = new Filters( imagelcon );
if t entry.filter.equals ( "BLUR" ) )
filters.blurlmage();
else if ( entry.filter.equals( "COLOR" ) )
filters.colorFilterO.
else if < entry.filter.equals( "INVERT" ) )
filters.invertlm»ge();
else if ( entry.filter.equals{ "SHARP" ) )
filters.sharpenlmage();
// обновление полей результирующей записи
entry.imagelcon = filters.getlmagelcon();
entry.processed = new Boolean( true ) ;
// помещение обноахениой записи в JavaSpaces
Lease writeLease = space.write( entry,
transactionCreated.transaction,
Lease.FOREVER );
leaseManager.renewUntil(
writeLease, Lease.FOREVER, null );
} // завершение if
// фиксация транзакции и завершение аренды
transactionCreated.transaction.commit();
leaseManager.remove{ transactionCreated.lease );
102
103
104
105 // обработка исключения
106 catch ( Exception exception ) {
107 exception.printstackTrace{};
108 )
109
HO )
111
112 ) //
113
114 public static void main{ String[J arga }
115 {
116 // получение миви хоста
117 if { arga.length != 1 ) {
118 System.out.printIn{
119 "Usage: ImageProcessor hostname" );
120 System.exit{ 1 );
121 )
122
123 ImageProcessor processor =
124 new Zmage?rocessor{ args{ 0 ) );
125
126 // ожидание изображения
127 processor.MaitForlmageO ;
128
129 ) II end nethod main
130 )
Рис. 4.21. Узел обработки изображений, который использует сервис JavaSpaces
Конструктор ImageProcessor (строки 29-42) получает сервис JavaSpaces и
сервис TransactionManager от заданного пользователем имени хоста. Классы Java-
SpaceFinder и Transactions!»nagerFinder определены, соответственно, в
листингах на рис. 4.1 и 4.15. Метод waitForlmage (строки 45—112) ожидает поступления
необработанного изображения. Цикл в строках 60-110 последовательно
обрабатывает записи из сервиса JavaSpaces. В строке 62 определяется шаблон записи,
который обработчик ImageProcessor будет использовать для извлечения
необработанных записей ImageEntry из сервиса JavaSpaces. В строках 63-65 извлекается
объект из сервиса JavaSpaces. Если запись, извлеченная из сервиса JavaSpaces, не
раана noil, в строках 70-84 фильтр применяется к изображению. В строках 87-88
для полей записи ImageEntry задаются значения, которые будут
идентифицировать ее как обработанную запись. В строках 91-93 обработанная запись Image-
Entry помещается обратно в сервис JavaSpaces. В строке 100 осуществляется
фиксация транзакции. В строке 101 завершается срок аренды.
Метод main (строки 114-129) получает задан нее пользователем имя хоста и
вызывает конструктор ImageProceesor. Метод waitForlmage начинает опрос сервиса
JavaSpaces на наличие необработанного изображения.
Класс Filters (рис. 4.22) осуществляет фильтрацию изображения. Он
предоставляет четыре типа фильтров — размытия, выделения границ, инверсии и
изменения цвета. Эти четыре фильтра можно найти в пакете cem.deitel.advjhtp.java2d
(см. главу 4 первой части книги). Конструктор Filters (строки 26-40) получает
объект Imagelcon и преобразует его в буферизованное изображение (объект типа
Bofferedlmage). Метод binrlmage {строки 43-46) применяет фильтр размытия
JavaSpaces
183
к изображению Bufferedlmage. Метод eharpenlmage (строки 49-53) применяет
фильтр выделения границ к изображению Bufferedlmage. Метод invertlmage
(строки 56-60) применяет фильтр инверсии к изображению Bufferedlmage. Метод
coiorFilter (строки 63-67) применяет фильтр изменения цвета к изображению
Buffered Image. Метод getlmagelcon (строки 70-78) возвращает фильтроваинсе
изображение в виде объекта Imagelcon.
1 // Filters.java
2 // Применение фильтров к наображенижи.
3 package com.deitel.advjhtpl.javaspaca.ImageProcestor;
4
5 // Базовые пакет» Java
6 import java.awt.*;
7 import java.awt.image.*;
8
9 // Стандартны* расширения Java
10 import javax.swing.*;
IX
12 // Пакеты Deitel
13 import сема.deitel.advjhtpl.java2d.*;
14
15 public class Filters (
16
17 Java2DZmageFilter blurFilter;
18 Java2DXmag«riltsr sharpenFilter;
19 Java2DImageFilter invartFilter;
20 Java2DImageFilter coiorFilter;
21
22 Bufferedlmage bufferedlmage;
23
24 // конструктор ккмпхалкаирует фильтр»! и преобразует
25 // изображения Imagelcon a Bufferedlmage
26 public Filters{ Inageicon icon )
27 t
28 blorFilter - new BlurFilterO;
29 sharpenFilter - new SharpenFilter{);
30 invertFilter = new InvartFiIter{);
31 coiorFilter = new CoiorFilter{);
32
33 Image image ■ icon.getlmageO ;
34 bufferedlmage - new Bufferedlmage{
35 ia\age.get«idth( null ), image.getBeightf null ),
36 BufferedImage.TYPE_INT RGB ) ;
37
38 Graphic«2D gg * bufferedlmaga.createGraphics();
39 gg.orawlmage{ iaage, null, null );
40 )
41
42 // оршювевме фидътра рааиытия к изображению
43 public void blnrlmage()
44 {
45 bufferedlmage = blurFilter.processImagef bufferedlmage );
46 )
47
48 // прямеяекме фильтра аыдехення границ к изображении
49 public void anarpenlmageO
184 Глава 4
50 {
51 buffacedlmage = eharpenFilter.proce»«I»age{
52 bufferedlmage );
53 )
54
55 // применение фильтра инверсии ояета к изображению
56 public void invertlmage()
57 t
53 bufferedlmage = invertFilter.process Image(
59 bufferedlmage );
60 )
61
62 // применение фильтра изменения цяета к изображению
63 public void colorFilter{)
64 t
65 bufferedlmage = colorFilter.proceaslmagef
66 bufferedlmage ) ;
67 )
69
69 // преобразование bufferedlmage в ImageIcon
70 public ImageIcon getlmagelcon{)
71 {
72 return { new ImageIcon( bufferedlmage }};
73 )
74 )
Рис. 4.22. Класс Filters применяет фильтры Java 2D к изображению
4.12.2. Разбивка изображения на фрагменты
Чтобы распарелделить задачу фильтрации большого изображения, мы можем
разбить изображение на фрагменты и дать возможность нескольким обработчикам
Image Processor одновременно осуществлять фильтрацию фрагментов
изображения. Затем мы можем объединить обработанные фрагменты, чтобы воссоздать
обработанное изображение. Класс ImageProcessorCUent (рис. 4.23) запрашивает
у пользователя файл изображения, подлежащий обработке, и разбивает
изображение на фрагменты. Класс ImageProcessorCUent оформляет фрагменты
изображения в виде записей ImageEntry и помещает их в сервис JavaSpaces. После того как
класс ImageProcessorClient записел все фрагменты изображения в сервис Java-
Spaces, он прочитывает все обработанные записи ImageEntry.
Конструктор ImageProcessorClient (строки 42-192) определяет
пользовательский интерфейс для получения входных данных от пользователя. В строках 66-81
отображается панель J Chooser Panel, когда пользователь нажимает кнопку Choose
file to process (Выбор файла для обработки), чтобы запросить у пользователя
файл изображения. В строках 90-1И определены компоненты JLabel и JText-
Field, которые получают задаваемое пользователем число фрагментов
изображений. В строках 114-135 создаются надпись и поле с раскрывающимся списком,
которое позволяет пользователю задать фильтр, применяемый к изображению.
В строках 142-186 в окно добавляется кнопка ОК. В строках 152-186
определяется действие, выполняемое при щелчке пользователя на кнопке ОК. Приложение
сначала проверяет, указал ли пользователь имя файла изображения и количество
фрагментов, на которые оно разбивается. Если да, приложение передает заданные
пользователем данные конструктору ImageSeparator (рис. 4.24) и вызывает
методы partition! mage и storelmage класса Image Separator для разбивки нзображе-
JavaSpaces 185
ния на фрагменты и сохранения их в сервисе JavaSpaces. Метод collect получает
ссылку на сервис JavaSpaces (строки 198—201). В строках 211—212 осуществляется
проверка, позволяет ли введенное пользователем число разделить изображение ни
столбцы и строки. Если введенное пользователем число не позволяет сделать это,
изображение будет разбито на 4 фрагмента. В строке 215 создается шаблон,
которому должны соответствовать все обрабатываемые фрагменты изображения.
В строке 218 создается моментальный снимок шаблона. В строках 221-225 все
обработанные записи ImageEntry извлекаются из сервиса JavaSpaces. В строках
251-265 осуществляется сборка изображение и вывод изображения в отдельном
окне. В строках 267-274 выводится сообщение об ошибке, если файл не найден.
Метод main (строки 278-294) получает заданное пользователем имя хоста из
командной строки и отображает его в окне приложения.
Класс ImageSeparator {рис. 4.24) разбивает изображение на фрагменты и
помещает их в сервис JavaSpaces. Конструктор (строки 33-39) получает имя файла
изображения, количество фрагментов и фильтр, применяемый к изображению. Метод
partition Image (строки 42-50) получает объект Iinagelcon из файла изображения
(строки 44-45) и вызывает метод parselmage класса ImageParser (рис. 4.25) для
разбивки изображения на фрагменты. Метод displaylmage (строки 53-60) создает
объект ImageDisplayer (рис, 4.26) для отображения исходного изображения.
Метод storelmage (строки 63-101) записывает фрагменты изображения в сервис
JavaSpaces. В строках 77-88 осуществляется запись фрагментов изображения
в сервис JavaSpaces и возобновляется аренда, срок аренды задается с помощью
консгапты Lease.FOREVER
1 // XmageProcessingClient.java
2 // Приложение запрашивает у пользователя файл изображения
3 // тип фильтра, число разбиений, фильтрует и отображает
4 //. изображение.
5 package com.deitel.advjhtpl.javaspace.ImagaProcessor;
6
7 // Базовые пакет» Java
8 import java.awt.*;
3 import java.awt.event.*;
10 import java.util.*;
11 import java.io.*;
12
13 // Пакеты расширений Java
14 import Java».swing.*;
15
16 // Базовые пакет Jini
17 import net.jini.core.lease.Lease;
18 import net.jini.core.entry.*;
19 import net.jini.core.transaction.*;
20 import net.jini.core.transaction.server.TranaactionManager;
21
22 // Пакеты расширений Jini
23 import net.jini.space.JavaSpace;
24
25 // Паиаты Deitel
26 import com.deitel.advjhtpl-javaspace.common.*;
27
28 public class XmageProcessingClient extends JFrarae {
29
30 private String[] operation» =» { -BLUR", "COLOR",
31 "IHVIRT", "SHARP" };
32 private JButton okButton;
33 private JComboBox operationComboBox;
34 private JTextField ImageText;
35 private JTextField numberText;
36
37 private static String hostname = '"•;
38 private String iatageNama,-
39 private String operation = "BLDR";
40 private int partitionHumber = 0;
41
42 public ImageProcessiagClient( String host )
43 {
44 super { "ImageProcessInput" };
45
46 hostname = host;
47 Container container « getContentPaoe{);
48
49 // центральная панель
50 JPanel centerPanel = new JPanelО;
51 centerPanel.setLayout{ new GridLayout{ 3, 2, 0, 5 ) );
52
53 // добавление надписи для изображают
54 JLabel imageLabel - new JLabel{ "Image File:",
55 SwingConstante.CENTER );
56 centerPanel.add( imageLabel );
57
58 JButton openTile ■ new J8utton(
59 "Choose file to process" );
60 openrile.addXctionListener{
61
62 new ActionListener0 {
63
64 public void actionPerformed( ActionEvent event )
65 {
66 JFileChooeer fileChooaer = new JFileChooaer 0 ,-
67
68 fileChooaer.setFileSelectionMode(
69 JTileChooeer.FILES_OKLY );
70 int result « £ileChooser.showOpanDlalog{ null
71 rile file;
72
73 // Польаожателъ щелкнул ma кнопке Cancel
74 if { result = JFileChooser.CANCEL_OPTIOH )
75 file = null;
76 else {
77 file = fileChooser.getSelectedTileO;
78 imageNama = file.getPath» ;
79 }
80
81 ) // аавереземме actionPerformed
82
S3 ) II завершение конструктора ActionListener
84
85 )i II аанарвекие addActionListener
86
87 centerPanel.add{ openrile );
JavaSpaces 187
88
89 // добавление надписи для числа фрагментов
90 JLabel numberLabel = new JLabel ( "Partition Huniber:"',
91 SwingConstants.CENTER );
92 centerPanel.add{ nuniberLabel );
93
94 // добавление текстового поля для числа фрагментов
95 numberText = new jTextField{ 10 );
96 centerPanel.add( numberText );
97
98 // ведение слушателя для текстового поля
99 nunberText.addActionListener{
100
101 new ActionListener{) {
102
103
104
105
106
107
108
109
110 )
in ) ;
112
113 // добавление надписи для тиса фильтра
114 JLabel operationLabel = new JLabel{ "Operation Type:",
115 SwingConstants.CENTER );
116 centerPanel.add{ operationLabel );
117
118 // добавления поля со списком
119 operationComboBox = new JConboBox{ operations );
120 operationComboBox.setSelectedlndexf 0 );
121 centerPanel.add{ operationComboBox );
122
123 // ваданне слушателя для поли со списком
124 operationComboBox.addltemListener(
125
126 new ItenListener() {
127
128
129
130
131
132
133
134 )
135 ) ;
136
137 // панель с ниопхамм
138 Л? ал el buttonPanel = new JPanelO;
139 buttonPanel.setLayout{ new GridLayout{ 1, 1, 0, 5 ) );
140
141 // добавление кнопки OK
142 okButton = new JButton{ "OK" );
143 buttonPanel.add{ okButton );
II получение текста, когда пользователь вводит
// символ возврата каретки • текстовом поле
public void actionPerformed( ActionEvent event )
{
partitionHumber = Integer.parselnt{
event.getActionCommand() );
J
// выбран любой фильтр, кроне равмнтия
public void itemstateChanged{ XteaEvent itemEvent )
t
operation =
( String ) operationComboBox.getSelectedltemO;
)
144
145 // добавление слушателя для
146 okButton.addActionListener t
147
14В new ActionListener{) {
149
150 // разбиение иаобранения на фрагменты, число
151 // которых указанно пользователем
152 public void actionpexfonned( ActionEvent event )
153 {
154 // получение ввода пользователя
155 partitionHumber = Integer.parseInt{
156 nufflberText.getText() );
157
158 // проверка, заполнил ли пользователь
159 // текстовые поля
160 if { ( partitionNumber = 0 )
161 || { unagaName = null } ) {
162 JOptionPane.showMessageDialogf null,
163 "Either image name or partition number "
164 + "is not specified!", "Error",
165 JOptionPane.ERROR_MESSAGE);
166 }
167
168 else (
169 setVisible( false );
170
171 // разбиение изображения ша фрагменты и
172 // и их сохранение в JavaSpaces
173 imageSeparator imageseparator =
174 new imageSeparator{
175 imageName, operation-, partitionNumber ) ;
176 imageSeparator.partitionlmage();
177 image Separator .storeXmage ( hostname ) ,-
178 image Separator .displayImage t) .'
179 collect();
180 }
181
182 } // занершение асtionPerformed
183
184 ) // завершение конструктора ActionListener
185
186 ); // завершение addActionListener
1B7
1ве // сборка
189 container.add{ centerPanel, BorderLayout.CENTER );
190 container, add { buttonPanel, Border Layout. SOUTH ),-
191
192 ) // завершение конструктора imageProcessingClient
193
194 // сборка обработанных изображений
195 public void collect()
196 {
197 // получение JavaSpaca
198 String jiniURL = "]ini.://" + hostname;
''* JavaSpaceFinder findtool =
200 new JavaSpaceFinder( jiniURL );
201 JavaSpace space = findtool.gebJavaSpace();
202
203 Vector unOrderedlmages
204 Vector orderedlmages =
205
206 // удаление фрагментов нзобраз
207 // заданное кия иа JavaSpace
20в try I
209 double squareRoot = Math.sqrt( parti tionNumbe г );
210
211 if ( Math.floor( squareRoot } •= ( squareRoot ) )
212 pa rti ti onKumber = 4;
213
214 // задание паблока
215 ImageEntry template = new ImageEntry( imageName, t:
216
217 // мгновенный снимок
218 Entry snapshotEntry = apace.snapshot! template );
219
220 // собираем фрагмента
221 for ( int i = 0; i < partitionHumber ; i++ ) (
222 ImageEntry remove = ( ImageEntry ) space.take(
223 anapshotEntry, null, Lease.FOREVER );
224 unOrderedlmages.add( remove );
225 }
226
227 int imageCount = unOrderedlmages .size() ,-
228 orderedlmages =
229 new Vector( imageCount );
230
231 // инициализация вектора
232 for ( int i = 0; i < imageCount; i++ )
233 orderedlmages.add( null );
234
235 // упорядочение фрагментов
236 for ( int i = 0; i < imageCount; i++ ) (
237 ImageEntry image =
238 < ImageEntry ) unorderedImages.«lementAt( i
239 orderedlmages.setElementAt(
240 image.imagelcon, image.number.intValue () );
241 )
242
243 ) // завершение блока try
244
245 // обработка исключений
246 catch ( Exception exception ) (
247 exception.printStackTraee();
248 }
249
250 // сборка фрагментов и отображение резульамручжего изо
251 if ( orderedlmages.size() > 0) (
252 ImageParser imageParser = new ImageParser();
253
254 Imagelcon icon = imageParser.putTogether(
255 orderedlmages );
256
257 laageDi«player uaageDieplayer ■
258 new ImageDiSplayer( icon );
259
260 imageDiSplayar.setSize( icon.getlcooHidtb() +
261 icon.getIconHeight() + 50 ) ;
262 imageDi*player.s«tVisible( true };
263 uaageDieplayer. satDafaultCloseOperation {
264 JFraM.EXIT_ON_CLOSZ ) ;
265 }
266
267 alsa {
268 JOptionPane.sao«HessageDialog( null,
269 "Invalid image name", "Error",
270 JOptionPane.KRROR_MESSAGE);
271
272 // завершение программы
273 System.exit( 0 ) ,-
274 )
275
276 ) // завершение сборки
277
278 public at*tic void main( String[] arge )
279 (
280 // полутегам имени хоста
281 if ( arga.length (= 1 ) (
282 System.out.println(
283 "Oaagei XmageProcesaingClient hostname" );
284 System.exit( 1 };
2B5 )
286
287 ImageProcasaingClient processor -
288 new imageProoassingClient( arga[ 0 ] );
289
290 // Залами* размеров охка и его отображение
291 processor.eetSizef 350, 150 );
292 processor.aetVisible( true );
293
294 } // аааериеша метода main
295 )
Рис. 4.23. Клиент распределенной системы обработки изображений
1 // ImageSeparator.Java
2// Этот класс разбивает изображение ка фрагменты
3 // и сохраняет их в JavaSpaoaa.
4 package coa.deitel.advjhtpl.javaapace.ZmageProcesaor;
6 // Базовые пакеты Java
7 import jav»,util.*;
8 import java.rmi.*;
9
10 // Стандартные расширения Javi
11 import javan.swing.*;
13 // базовые пакета Jini
14 import net.jini.core.lease.Lease;
15 import net.jini.core.transaction.TransactionException;
16
17 // Пакет расширений Jini
18 import net.jini.apaca.javaSpace;
19 import net.jini.lease.*;
20
21 // пакеты Deitel
22 import coa.deitel.advjhtpl.javaspace.common.*;
23
24 public class ImageSeparator (
25
26 private String imageName;
27 private String filterType;
20 private int partitionNumbeг;
29 private Vector imagePieces;
30 private Imagelcon icon;
31
32 // Конструктор ImageSeparator
33 public ImageSeparator(
34 String name, String type, int number )
35 (
36 imageHama * name;
37 filterType ■ type;
3B partitionNumber = number;
// разбиение изображения иа фрагмент!
public void partitionXmage()
(
ImageParsar imageParser a new ImageParser();
icon = new Imagelcon( imageNama );
// разбиение наображений
imagePieces = imagaPareer.parseImage(
icon, partitionNumber );
// отображение изображения
public void display Image( }
(
ImageDisplayer imageDisplayer =
new ImageDi splayer( icon );
imagaDisplayar.BetSize( icon.getIconWidth() + 50,
icon.getlconBeight() + SO );
imageDieplayer.setVisible( true };
' запись фрагментов a JavaSpacas
iblic void atoralmage( String hostname )
// получение JavaSpace»
String jiniURL a "jini://" + hostname;
JavaSpaoeFinder findtool ■
new JavaSpaceFindar( jiniQRL );
JavaSpace space = findtool.getJavaSpace();
LeaseRenewalManager leaseManager =
new LeaseRenewalManager{);
// запись фрагментов в JavaSpaces
try (
for { int i = 0; i < imagePieces.size(); i++ ) {
Imagelcon suMmage =
{ Imagelcon ) imagePiecee.elamanfcftt( i );
ImageEntry imageEntry = new ImageEntry(
imageltame, filterType, i, false, sublmage ),-
Lease writeLeasa - space.write(
imageEntry, null. Lease.FOREVER );
leaseManager.renewUntil(
writeLease, Lease.FOREVER, null );
// сбой а сети
catch ( RamotaException remoteException ) {
remoteException .printstackTrace () ,-
96 // Операция записи при некорректной транзакции
97 catch ( TransасtionException transaetionException ) {
98 transactionException.printstackTrace();
99 )
100
101 } // завершение метода storeImage
102 1
Рис. 4.24. Разбивка изображения на фрагменты и сохранение их в сервисе JavaSpaces
Класс ImageParser (рис. 4.25) разбивает изображение на фрагменты для
обработки, а после обработки восстанавливает изображение из фрагментов. Метод
parselmage (строки 25-66) разбивает изображение на фрагменты. Если из числа
фрагментов, заданных пользователем, нельзя надело извлечь корень квадратный,
используется умалчиваемое значение 4 (строки 29-35). В строках 42-62
осуществляют разбивка изображения на фрагменты. Полученные фрагменты сохраняются
в контейнере типа Vector. Метод putTogether (строки 71-106) собирает
фрагменты в изображение. В строках 93-102 фрагменты из входного вектора Vector
помещаются в объект Bufferedlmage для формирования полного изображения. В
строке 104 изображение возвращается как объект Imagelcon.
1 // ImageParser.Java
2 // Данный класс разбивает изображение на фрагменты
3 package com.deitel.advjhtpl.javaspace.ImageProcessor;
i
5 // Базовые пакеты
6 import Java.awt.image.*;
1 import java.net.URL;
8 in^>ort java.awt.*;
9 Import java.lang.*;
10 import java.util.Vector;
11 import java.awt.geom.*;
12
13 // Стандартные расширения Java
14 import j&vax.awing.*;
15
16 public class I«agePare»с (
17
18 Imagelcon image;
19
20 public ImageParser() {)
21
22 // передача методу parееImage иаображекия Imagelet
23 // числа фрагментов, на которое должно
24 // бить развито изображение
25 public Vector parееImage(
tagelcon imagelcon, int numberPieces )
{
Vector vector = new Vector();
double aquar«Root = Hath.sort( numberPieces );
if ( Math.floor( squareRoot ) != ( equareRoot ) ) (
System.out.println( "This is not a square number,"
+ " setting to default...");
numberPieces ■ 4;
1
// получение чиола строк и столбцов
int numberRows = ( int ) Math.sqrt( numberPieces );
int numberColumns = ( int ) Hath.sqrt( numberPieces );
// получение изображения на Bufferedlmage
Image image = imagelcon.getlmage();
Bufferedlmage bufferedlmage ■ new Bufferedlmage(
image.getWidth( null ), image.getHeight( null ),
BufferedImage.TYPE_IHT_RGB );
Graphics2D g2D = hufferedlmage.createGraphics{);
g2D.drawImage( image, null, null );
// получение размеров фрагмента
int width * bufferedImage.getHidth() / numberColumns;
int height * bufferedlmage.gatHaight() / numberRows;
// получение фрагментов
for ( int x = 0; x < numberRows; x++ ) (
for ( int у - 0; у < numberColumns; y++ ) (
vector.add( new Imagelcon(
bufferedlmage.getSubimage(
x * width, у * height, width, height ) ) );
>
return vector;
66 } // завершение метода parse Image
67
68 // получает вектор фрагментов
69 // и возвращает собранное
70 // изображение
71 public Imagelcon putTogether( Vector vector )
72 {
73 double size = vector.size();
74 int numberRowColumn = < int ) Hath.sqrt( size );
75
76 // получаем первсе изображение
77 Image templmage =
78 ( ( Imagelcon ) vector.get( 0 ) ).getlmage();
79
80 // получение размеров фрагмента
81 int width = templmage.getWidth( null );
82 int height = templmage.getHeight( null };
83
84 // создание буферизованного изображения
85 Bufferedlmage totalPicture = new Bufferedlmage(
86 width * numberRowColumn, height * numberRowColumn,
87 BufferedImage.TYPE_lNT_RGB );
88
89 // создание объекта Graphics
90 Graphics2D graphics = totalPicture.createGraphics{];
91
92 // сборка изображения из фрагментов
93 for ( int x = 0; x < numberRowColumn; x++ ) {
94
95 for ( int у = 0; у < numberRowColumn; y++ } {
96 Image image = ( ( Imagelcon ) vector.get(
97 у + numberRowColumn * x ) ).getlmage();
98 graphics.drawlmage( image,
99 AffineTransform.getTranslateInstance(
100 x * width, у * height ), null };
101 )
102 )
103
104 return new Imagelcon( totalPicture );
105
106 ) // завершение метода putTogether
107 )
Рис. 4.25. Рззбивхэ и сборка изображения
Класс ImageDisplayer (рис. 4.26) представляет собой подкласс класса JFrame
для отображения изображения. Конструктор (строки 15-30) принимает
изображение (объект Imagelcon} и отображает его в компоненте JLabel.
1 // ImageDisplayer.Java
2 // Денисе приложение предоставляет пользовательский
3 // ихтерфейс для отобратания изображения.
4 package com.deitel.advjhtpl.javaspace.ImageProcessor;
5
6 // Пакет расширений Java
7 import javas.swing.*;
9 // Базовые пакеты Java
10 iaport java.awt.*;
11 iaport java.awt.event.*;
12
13 public class ImageDisplayer енtends JFrame {
14
15 public ImageDisplayer{ Imagelcon icon)
16 {
17 super( "ImageDieplay" );
18 Container container = getContentPane();
19
20 // центральная панель
21 JPanel centerPanel = new jPanel();
22
23 // добавление компонента для изображения
24 JLabel imageLabel = new JLabel(
25 icon, SwingConstants.LEFT );
26 centerFanel.addf imageLabel );
27
28 container.add( centarPanel, BorderLayout.CENTER );
29
30 ) // завершение конструктора ImageDisplayer
31 )
Рис. 4.26. Вывод изображений
4.12.3. Компиляция и выполнение примера
Перед компиляцией кода примера убедитесь, что пути к файлам jlui-tore.jar,
jini-ext.jar и sun-util.jar включены в переменную окружения CLASSPATH.
Чтобы выполнить пример, запустите:
1. Web-сервер.
2. демон активации RMI.
3. сервис поиска Jini,
4. сервис JavaSpaces и
5. сервис Transaction Jini.
На втором компьютере выполните следующие действия:
Откомпилируйте все файлы Java в каталоге com\deitel\advjhtpl\javaspace\
ImageProcessor.
Запустите приложение ImageProcessorClient, указав имя хоста,
выполняющего сервис поиска Jini (нужно также указать соответствующий файл политики).
По меньшей мере на одной из машин запустите ImageProcessor и укажите имя
хоста, выполняющего сернис поиска Jini (нужно также указать соответствующий
файл политики).
Важным моментом, на который следует обратить внимание в этом примере,
является то, что увеличение вдвое числа узлов ImageProcessor, которые извлекают
записи из сервиса JavaSpaces, удваивает вычислительную мощность приложения.
Если вы не располагаете несколькими компьютерами, то можно выполнить все
приложение на одном компьютере. Хотя это и сводит на нет преимущества
распределенной системы, выполнение приложения на одном компьютере может быть
полезным при его тестировании и отладке. На рис. 4.27 показан пользовательский
интерфейс приложения ImageProcessorClient.
^J. SSfewli
ЙЙййЬ—- !
ц? »>5^™?™**^? к**г^
\-irt. 'Z3* ** '
\А)г.
..**, -e
H
Ih-j
Рис. 4.27. Пользовательский интерфейс приложений ImageProcessorMaln
и I mage Co Hector
На рис. 4.28 слева показано изображение до того, как приложение Image-
ProcessorCIient распределяет фрагменты изобрежения по узлам Image Processor.
Узлы ImageProcessor применяют к изображению фильтр размытия. Справа на
рисунке показано результирующее изображение после того, как приложение Image-
Processor Client осуществило сборку фрагментов
Рис. 4.28. Изображения до и после размытия
4.13. Ресурсы в Internet и во Всемирной паутине
www.sun.con/jini/specs/jsl_l.pdf
На этом сайте можно найти спецификацию сервиса JavaSpaees Jini.
www. 3awawoEld.com/iw-ll-i999/3w-ll-31.niology.htn1l
www.jawaworld.cora/jw-01-2000/jw-Ql-jiniology.html
www. 3awaworld.eon/jw-03-2000/jw-03-jiniology.html
www.javavoz-ld.eon/jw-04-2000/3W-0421-jiniology.htnLL
www. javavoEld.com/jw-06-2000/jw-0623-jiniology.htnLL
Эти URL виртуального журнала JavaWorld представляют ряд статей, знакомящих
с технологией JavaSpaees, включая такие темы, как доступные операции, транзакции
и аренда.
www.byte.coni/docuraents/sxl46/BYT19990921S0001/lndwx.htm
В этой статье представлен обзор технологии JavaSpaees и освещены е
е представляет собой сервис Jini, который реализует простую,
высокоуровневую архитектуру для построения распределенных систем. Сервис JavaSpaees дает
возможность объектам Java взаимодействовать, совместно использовать объекты и
координировать задачи, используя область совместно используемой □
JavaSpaces
197
• Сервис JavaSpaces предоставляет три основные операции: запись (write), изъятие (take)
и чтеаие (read). Операция записи write помещает объект, называемый записью, в сервис
JavaSpaces.
• Операция изъятия take задает шаблон и удаляет из сервиса JavaSpaces запись, которая
соответствует заданному шаблону. Операция чтения read сходна с операцией изъятия, но
не удаляет соответствующую шаблону запись из сервиса JavaSpaces.
• В дополнение к трем основным операциям сервис JavaSpaces поддерживает проведение
транзакций посредством менеджера транзакций Jini и механизма уведомлений, который
извещает объекты, когда в сервис JavaSpaces помещается запись, отвечающая заданному
шаблону.
• Запись, хранящаяся в сервисе JavaSpaces, будет оетаяаться в нем до тех пор. пока не
истечет ее срок аранды, или пока программа не осуществит изъятие записи из сервиса
JavaSpaces.
• Сервис JavaSpaces находит объекты, сопоставляя их с шаблоном. Шаблон задает
критерий поиска, на соответствие которому сервис JavaSpaces проверяет каждую запись. Если
шаблону соответствует одна няи несколько записей, сервис JavaSpaces возвращает одну
отвечающую шаблону запись.
• Сервис JavaSpaces может использовать менеджер транзакций Jini для поддержки
проведения последовательности операций.
• Объекты в сервисе JavaSpacea я ваяются озвместно используемыми. Программы могут
читать я изымать записи из сервиса JavaSpacea, модифицировать открытые поля в этих
записях и помещать их обратно в сервис JavaSpaces для использоззния другими программами.
• Любой объект, хранящийся 8 JavaSpaces, должен реалгаовывать интерфейс Entry {пакет
net jlnl.core.entry). Записи (объекты Entry) сервиса JavaSpaces следуют контракту Entry
Jini, определенному я спецификации Jini Core Specification.
• Запись (объект Entry) может иметь несколько конструкторов и столько методов, сколько
необходимо. К другим требованиям относятся: наличие открытого конструктора без
параметров, открытые поля и отсутствие полей с примитивными тинами. П ос рад пи к сервиса
JavaSpaces использует конструктор без параметров для осздания в процессе десериалиэа-
ции экземпляра записи, соответствующей шаблону.
• Все поля, которые будут использоэаться в вачестве полей шаблона, должны иметь тип
public (подробно о шаблонах говорится в разделе 4.8). Согласно определению
спецификации Jini Core Specification, запись не может иметь полей с примитивными типами.
• Технология JavaSpaces, как и Jini, основывается на нескольких базовых сервисах.
Сервис JavaSpaces взаимосвязан с сервисом поиска Jini. Если необходимо провести
транзакцию, должен быть запущен сервис транзакций Jini. Сервис JavaSpaces также зависит от
Web-сервера и rmid.
• Имеется две версии сервиса JavaSpaces: временный сервис JavaSpaces (неактнвируемый)
и постоянный сервис JavaSpaces (активируемый).
• Временный сервис JavaSpaces не требует демона активации RMI (rmid), поскольку
временный сервис JavaSpacea не является активируемым. После завершения работы
временного сервиса JavaSpacea вся его информация о состоянии теряется, и rmid не способен
запустить сервис.
• Постоянный сервис JavaSpaces является активируемым и, следовательно, требует демона
активация RMI. После завершения работы постоянного сервиса JavaSpaces вся его
информация о состоянии озхраняется в файле журнала, a rmid может перезапустить сервис
поэднве.
• После инициализации каждый сервис JavaSpacea регистрирует себя с помощью
локального сервиса поиска.
• Клиенты осуществляют доступ к объектам в сервисе JavaSpaces через интерфейс Java-
Spaco (пакет net.Jtni^paco). Интерфейс JavaSpace предоставляет несколько методов:
notify, read, readlfExltt*, take, takelfExlBte, write и snapshot.
• Операция write вставляет запись в сервис JavaSpaces. Вели идентичная запись уже
существует в сервисе JavaSpaces, era операция не переписывает существующую запись, а
помещает в сервис JavaSpaces копию записи.
• Операция read пытается прочесть запись, соответствующую шаблону записи, из сервиса
JavaSpaces. Вели соответствующей шаблону аавиен в сервисе JavaSpacea нет. эта
операция эозвращает па 1). Если в сервисе JavaSpacea существует несколько записей,
соответствующих шаблону, операция read произвольно выбирает одну из них. Метод read
выполняется до тех пор, пока в сервисе JavaSpacea не будет найдена соответствующая шаблону
запись, либо пока не наступит тайм-аут.
Глава 4
Метод readlfExists проверяет, существует ли в сервисе JavaSpeces запись,
соответствующая шаблону. Есяи такая запись в сервисе JavaSpaces не существует, метод readlfExists
должен сразу же возвраткть nail. Метод readlfExists не является блокирующим, если
только соответствующая шаблону запись не является участником незавершенной
транзакции.
Операция take пытается удалить запись, которая соответствует шаблону, из сервиса
JavaSpaces. Эта операция работает так же, как и операции owd, но при этом удаляет
соответствующую шаблону запись из сервиса JavaSpaces.
Метод take является блокирующим и выполняется, пока в сервисе JavaSpaces не будет
найдена соответствующая шаблону запись, или пока не наступит тайм-аут.
Метод takelfExists пронеряет, существует ли в сервисе JavaSpaces запись,
соответствующая шаблону. Вели такая запись не существует, метод takelfExists должен сразу же
возвратить null. Метод takelfExists не блокируется, если только соответствующая шаблону
запись не является частью незаконченной транзакции.
Операция notify предписывает сервису JavaSpaces посылать уведомление
объекту-слушателю, когда клиент записывает соответствующую шаблону запись в сервис JavaSpaces.
Этот метод позволяет приложению избавиться от необходимости постоянно проверять
наличие записи в сервисе JavaSpaces.
Метод snapshot повышает производительность, если программе требуется постоянно
выполнять сериалиаацию одной записи (объекта Entry). Каждый раз, когда программа
передает запись в сервис JavaSpaces (например, записывая этот объект Entry или используя
его в качестве шаблона), этот объект Entry должен быть сериализоеан. При вызове метода
snapshot объект Entry сериализуегся один раз, этот сериалиеовапиый объект Entry
может многократно использоваться в будущем.
Метод write принимает три параметра: запись (объект Entry), объект Transaction и
значение типа long, которое задает продолжительность времени, в течение которого сервис
JavaSpaces должен хранить запись. Значение типа long укаэыаяет продолжительность
аренды для записи.
Операции read u take извлекают записи из сервиса JavaSpaces. Клиент может прочитать
или извлечь запись из сервиса JavaSpaces, предоставив шаблон записи, с которым будут
сопоставляться открытые поля записей в сервисе JavaSpaces. Шаблон задает, какие
именно поля должны использоваться при сопоставлении.
Процесс извлечения основывается на механизме сопоставления с шаблоном, когда
отыскиваются записи, киеющие соответствующие значения в полях public. Поля в шаблоне,
имеющие значения, не равные null, должны в точности совпадать с полями записей в
сервисе JavaSpaces. Поля в шаблоне, значения которых устаяовлекы в nail, действуют в
качестве групповых символов.
Если в сервисе JavaSpaces существует набор записей одного и того же типа, при
сопоставлении с записями или группой записей, содержащихся в сервисе JavaSpaces, используют,
ся только те поля, которые соответствуют полям шаблона. Поля в шаблоне,
установленные в null, могут соответствовать записям в сервисе JavaSpaces, имеющим любое значе-
Операдия read получает записи, не удаляя их из сервиса JavaSpaces. Методы read и
readlfExists выполняют операцию чтения. Каждый из методов принимает три параметра:
объект Entry, задающий шаблон соответствия, объект Transaction и значение типа long.
Метод read задает период времени, в течение которого операция чтения блокируется,
прежде чем возвратить null. Метод readlfExists предстааляет собой неблокируемую версию
метода read. Если залисей, соответствующих шаблону, нет, метод readlfExists сразу же
возвращает nnll.
Метод readlfExists блокируется только в том случае, если разработчик указал период
времени, в течение которого метод readlfExists будет ожидать, если соответствующая
шаблону запись является частью незаконченной транзакции. Если соответствующая шаблону
запись не участвует л кякой-либл тр$из$чп,ии, операция read сразу же возвращает эту
sail метод read, и метод readlfExists возвращают только одну запись. Если шаблону
соответствует несколько записей, операция read выбирает одну из них произвольным, образом.
Операция изъятии take получает запись я удаляет es из сервиса JavaSpaces. Операцию
изъятия выполняют методы take и takelfExists. Методы take и takelfExists схожи с ме-
тодаыи read или readlfExists. Единственное отличие состоит в том, что соответствующая
шаблону запись, возвращаемая операцией take и takelfExists, удаляется из сервиса
JavaSpaces.
• Операция уведомления notify предписывает сервису JavaSpaces посылать уведомление
слушателю, когда клиент записывает соответствующий шаблону объект Entry в сервис
JavaSpaces. Метод notify принимает пять параметров: объект Entry, задающий шаблон,
объект Transaction, слушатель, который реализует интерфейс RemoteEvcntListener
(пакет net.jini.core.event), значение тина long, которое задает длительность аренды для
регистрации слушателя, и объект MarshalledObject (пакет java.rmi), который сервис
JavaSpaces будет передавать удалена ом у слушателю при уведомлении.
• Метод snapshot принимает шаблон и возвращает особое представление записи
(моментальный снимок ваписи). Этот моментальный снимок записи может быть использован
только н сформировавшем его сервисе JavaSpaces.
Терминология
abort, метод класса Transaction take, метод интерфейса Java Space
commit, метод класса Transaction takelfExists, метод интерфейса JavaSpace
Entry, класс (net.jim.core.entry) template matching mechanism — механизм
JavaSpaca, интерфейс (net.jini.epace) сопоставления с шаблоном
JavaSpscea service — сервис JavaSpaces transaction — транзакция
notify, метод интерфейса JavaSpace Transaction .Created, класс
outrigger JavaSpace implementetion — pea- Transact ion.Created, transact ion, интерфейс
лизация интерфейса JavaSpace TransactionException, класс
read, метод интерфейса JavaSpace TransactienManager, сервис
readlfExiets, метод интерфейса JavaSpace transient JavaSpaces service — временный
RemoteE vent Listener, интерфейс сервис JavaSpaces
(net .jini.се re .event) UnnsableEn try Except ion, класс
snapshot, метод интерфейса JavaSpace write, метод интерфейса JavaSpace
Упражнения для самоконтроля
4.1. Заполните пропуски в следующих предложениях:
а) Технология JavaSpeces представляет собой технологию, ориентированную на
h) Объекты, помещаемые в сервис JavaSpaces, должны реализовывать интерфейс
c) Двумя методами интерфейса JavaSpace, осуществляющими чтение »
виса JavaSpaces, являются и .
d) Для создания транзакции требуется _
е) Метод может быть использован, чтобы набежать и
ций объекта Entry.
4.2. Ответьте, является ли каждсе из приведенных ниже высказываний ь
ложным. Если высказывание ложно, объясните, почему.
a) Записи должны иметь закрытые (private) поля, которые используются в шабло-
b) Метод take удаляет все соответствующие шаблону записи из сервиса JavaSpaces.
c) Чтобы модифицировать запись в сервисе JavaSpaces, необходимо изъять запись из
сервиса JavaSpaces, изменить значения записи и поместить вапись обратно в сервис
JavaSpaces.
d) Объекты, хранящиеся в сервисе JavaSpaces, не могут иметь полей примитивных типов.
e) Если при записи объекта Entry в сервис JavaSpaces этот объект уже существует,
новая запись замещает старую.
Ответы на упражнения для самоконтроля
4.1. а) объекты и сообщения, b) Entry, с) read, readlfExiste. d) ыенеджер транзакций.
е) snapshot.
4.2. а) Ложно. Поля в записи, используемые для сопоставления с шаблоном, должны быть
открытыми (public), поскольку сервису JavaSpaces необходимо осуществлять доступ
Глава 4
к зтим полям. Ь) Ложно. Если шаблону соответствует несколько записей, метод take
удаляет только одну запись, с) Истинно, d) Истинно, е) Ложно. В сервисе JavaSpaces
может содержаться несколько копий одной и той же записи. Если запись уже
существует, операция записи write помещает еще одну запись в сервис JavaSpaces, если
только в процессе записи ие возникает исключение.
Упражнения
4.3. Поясните различие между методом take и методом takelfExists интерфейса Java-
Space.
4.4. Запустите сервис JavaSpaces с именем, отличным от имени по умолчанию, и найдите
сервис JavaSpaces с помощью сервиса поиска Jini, используя Name Entry.
4.5. В приложении WriteOparation (рис. 4 3) записи (объекты Entry), помещенные в сервис
JavaSpaces. хранятся в нем максимум 5 минут. Используя менеджер возобновления
аренды LeaseRenewalManager, перепишите приложение, чтобы записи хранились
в сервисе JavaSpaces неопределенно долго.
4.6. Напишите программу, которая использует метод take для удаления всех записей,
которые соответствуют определенному шаблону. Сравните скорость выполнения этого
приложения оо скоростью выполнения приложения SnapshotEntry.
4.7. Напишите программу, которая использует метод notify для отслеживания всех
записей, помещенных в сервис JavaSpaces. Можете ли вы скавать, что имеются какие-либо
записи, которые слушатель ве отследил?
4.8. В наших примерах мы использовали класс JavaSpaceFinder для поиска сервиса
JavaSpaces и класс TransactlonManagerFinder для поиска менеджера транзакций. Эти
два класса похожи. Напишите программу с именем ServiceFinder, которая принимает
объект типа Class (т.е. Java Spa се. с lass) и возвращает объект сервиса (т.е. объект типа
Java Space). Возвращает ли класс ServiceFinder один и тот же объект Java Space, если
выполняется несколько сервисов JavaSpaces? Если нет, как сделать так, чтобы класс
ServiceFinder всегда возвращал тот сервис JavaSpaces, который вам нужен? [Подск/и
ка. Задайте имя для каждого экземпляра сервиса JavaSpaces с помощью свойства
coin.sHH.jinJ.ontrigger.spaceNaine при запуске сервиса JavaSpaces из командной
строки. Это свойство позволяет зарегистрировать соответствующую заглушку Java Spa се
в сервисе поиска Jini, установив в качестве имени записи (Name Entry) заданное значе-
4.9. Налишите программу, которая считывает все записи из сервиса JavaSpaces, не удаляя
их из сервиса.
Использованные источники
1. <java.sun.oom/products/javaspaces/f&qs/jsfaq.html>.
2. Oon».javaworld.com/javat»or.lcl/3*-ll-l999/jw-ll-]iniology_p.html>.
Литература
Freeman, E., S. Hupfer, and K. Arnold. JavaSpaces Principles. Patterns, and Practice.
Reading, MA: Addison Wesley Publishing, 1999.
Edwards. W. K. Core Jini (Second Edition). Upper Saddle River, NY: Prentice-Hall. Inc.,
2001.
Li, S. Professional Jini. Birmingham, UK: Wrox Press Ltd. 2000.
Newmarch, J. Programmer's Guide to Jini Technology. New York, NY: Springer-Verlag
New York, Inc., 2000.
Oaks, S., and H. Wong. Jini in a Nutshell. Sebaetopol, CA: O'Reilly & Associates, Inc.,
2000.
5
Java Management Extensions
(JMX)
Цели
• Разобраться с архитектурой
JMX.
• Освоить паттерн
проектировании МВеапз.
• Научиться ыаходить в сервисе
JavaSpaces записи,
соответствующие шаблонам.
• Научиться делать ресурсы
управляемыми с помощью
интерфейса управления
и МВеапз.
• Разобраться с архитектурой
агентов J MX.
• Научиться создавать
и использовать агенты JMX.
• Научиться создавать
приложения,
взаимодействующие
с агентами JMX.
Воображение — мощное средство
создания воображаемых миров из
материала, предоставляемого
природой.
Иммануил Кант
Факты ничего не говорят
сами по себе, пока не связаны
закономерностью.
Луис Атас сиз
Ничего примечательного в этом нет,
просто нужно нажимать на нужные
клавиши тогда, когда надо,
и инструмент заиграет сам.
Иоганн Себастьян Бах
202
Глава 5
5.1. Введение
Сети играют все большую роль в бизнесе. Бизнес все больше нуждается в легко
доступных и обновляемых сетевых сервисах, настраиваемых иа потребности
клиентов. По мере того» как все большее число организаций начинают использовать
сети для повышения эффективности своей деятельности, управление сетями
становится все более и более сложным. Кроме того, все больше различных устройств
могут быть подключены к сети. Корректное функционирование принтеров,
маршрутизаторов, а также других устройств играет важную роль в обеспечении
эффективной работы организации. По мере подключения к сетям все большего числа
различных устройств управление сетями становится нее более сложной задачей.
Существующие средства управления сетевыми устройствами зачастую используют
проприетарные протоколы и программное обеспечение. Разнородность таких
протоколов и устройств двлает задачу управления сетью весьма сложной.
Другой проблемой, усложняющей упрннленне сетями, является большое число
схем управления, не являющихся гибкими и не поддающихся автоматизации. Это
приводит к тому, что обеспечение нормального функционирования больших сетей
требует значительных затрат времени и ресурсов. Требуются новые технологии,
снижающие трудоемкость решения рутинных задач сетевого управления.
Современные средства управления сетями являются обычно распределенными
и используют автономные программы, называемые агентами. Агент передает
информацию о состоянии сетевых ресурсов управляющему приложению и
осуществляет управление по командам управляющего приложения. Только управляющее
приложение принимает решения в архитектуре управления, использующей
агенты. Такие агенты, назызаемые статическими, они не могут самостоятельно
реагировать на события в сети. Примером является агент SNMP (Simple Network
Control Protocol — простой протокол сетевого управления). Агент SNMP является
статическим агентом, назначением которого является обеспечение
взаимодействия между сетевым устройством и управляющим приложением. В архитектуре,
использующей статические агенты, все управляемые ресурсы должны быть
созданы и сконфигурированы на этапе разработки. Это делает задачу расширения
управляющих приложений для работы с новыми сетевыми ресурсами достаточно
трудной.
Java Management Extensions (JMX) 203
Технологии, разработанные в последние годы, дают в руки разработчикам
программного обеспечения возможность разрабатывать интеллектуальные агенты для
решения различных задач сетевого управления. Такие агенты образуют
структуру, в которой агенты взаимодействуют друг с другом, откликаясь на события,
происходящие в сети, и управляя сетевыми ресурсами.
Разработанные корпорацией Sun и другими лидерами индустрии сетевых
технологий Java Management Extensions {JMX) определяют инфраструктуру
компонентов, позволяющую разработчикам создннать автоматизированные,
интеллектуальные решения для сетевого управления в быстро меняющихся ситуациях. JMX
определяет трехуровневую архитектуру: уровень сетевых ресурсов, уровень
агентов и уровень управления (рис. 5.1)1. Уровень сетевых устройств делает объект
управляемым, уровень агентов предоставляет управляющие сервисы, делающие
ресурсы доступными для управления. Наконец, уровень управления дает
возможность управляющему приложению осуществлять доступ к управляемым ресурсам
и взаимодействовать с ними через агенты JMX. Кроме того, JMX обеспечивает
поддержку существующих протоколов сетевого управления, например, SNMP, так
что JMX позволяет осуществлять интеграцию существующих систем сетевого
упрелления.
В соответствии со спецификацией JMX обеспечивает:
1. независимость от платформы, так как для функционирования
используются технологии Java;
2. независимость от протоколов, так как поддерживается набор протоколов;
3. повторное использование кода, так как управляемые ресурсы следуют
спецификации JavaBeans;
4. некоторую интеллектуальность агентов, так как JMX позволяет
взаимодействовать с ресурсами непосредственно, не ожидая принятия решения
приложением сетевого управления;
5. масштабируемость, так как агенты могут быть использованы с любым
устройством, на котором функционирует виртуальная машина Java.
Уровень управления Управляющее приложение
Уровень агентов ( Агент управления Java
Рис. 5.1. Трехуровневая архитектура JMX
1 Sun Microsystems, Inc. «JMX Instrumentation and Agent Specification v. 1.0», July 2000
<jcp. org/aboutJava/coiranuiii typrocess/ final/ jsrO 03 /index . html>
204
Глава 5
5.2. Установка
Существует большое число реализаций спецификации JMX. Одной из них
является реализация корпорации Sun Java Dynamic Management Kit (JDMK). JMX
предоставляет стандартный интерфейс прикладного программирования для
создания приложений сетевого управления, однако различные производители
программного обеспечения предоставляют дополнительные возможности, не
определенные в стандарте JMX и решающие определенные задачи сетевого управления.
Для создания системы сетевого управления, использующей JDMK, необходимо
прежде всего загрузить Java Dynamic Management Kit. Ознакомительную версию
Java Dynamic Management Kit 4.2 можно загрузить по адресу:
tnrw.sun.com/software. java-dynamic/try.btsl
Java Dynamic Management Kit совместим с платформами Solaris SPARC, Linux,
Windows 2000 н Windows NT, Для загрузки JDMK для платформ Windows 2000
и Linux выберите гиперссылку «Other Platform*, после чего загрузите файл,
соответствующий вашей платформе.
Установочный комплект для платформы Windows 2000 представляет собой файл
архива в формате zip. Раз архивирование приводит к созданию каталога SUN-
Wjdmk, который содержит подкаталоги и файлы, необходимые для разработки
систем сетевого управления с помощью JDMK. Пути к файлам jdmkrt.jar и jdmktk.jar
(расположенные в каталоге SUN-Wjdrak\jdmk4.2\1.2\lib) должны быть заданы
в переменной CLASS PATH перед компиляцией н выполнением примеров.
5.3. Практический пример
В последующих разделах мы разработаем небольшую систему сетевого
управления с использованием Java Dynamic Management Kit. Система включает в себя
управляемые ресурсы, агенты управления и управляющее приложение. В данном
примере управляемым ресурсом является принтер. В функционирующей системе
не очень приветствуется экспериментировать с принтером, так что мы
ограничимся управлением моделью принтера. Принтер управляется компонентом МВеап.
Управляющее приложение взаимодействует с принтером через MBeanServer —
реестр компонентов МВеап. Сервис RMI, входящий в состав JDMK, делает
возможным удаленное управление принтером. На рис. 6.2 изображена архитектура
системы сетевого управления.
В разделе 5.3.1 рассмотрено, как сделать объект управляемым, отображая
управляемый ресурс. В разделе 5,3.2. рассмотрено создание агента управления
JMX с сервером MBeanServer и сервисом RMI, входящим в состав JDMK. В
разделе 5.3.3 описано, как агент получает уведомления, посылаемые управляемым
ресурсом. В разделе 5.3.4 рассматривается управляющее приложение,
взаимодействующее с агентом, наконец, в разделе 5.3.5 рассказано, как откомпилировать и
запустить на выполнение практический пример.
5.3.1. Ресурсы
Уровень сетевых ресурсов делает ресурс управляемым. В качестве ресурса
может выступать устройство, приложение или любой объект Java, который должен
управляться управляющим приложением. Уровень сетевых ресурсов
предоставляет интерфейс управления, чтобы управляющие приложения могли
взаимодействовать с ресурсом. Интерфейс управления содержит свойства, описывающие ресурс,
и операции управления ресурсом.
Java Management Extensions (JMX)
f Управляющее приложение )
RmiConnectorClient S555
RmiConnectorServer 5555
Рис. 5.2. Архитектура системы сетевого управления практического примера
Уровень сетевых ресурсов обычно реализуется в виде стандартного
компонента МВеап, который также называют управляемым компонентом JavaBean,
представляющим управляемые объекты Java. Стандартный компонент МВеап состоит
из двух частей: интерфейса МВеап и класса Java, реализующего интерфейс МВеап
(называемый классом МВеап)1. Стандартный компонент МВеап должен следовать
паттерну проектированию, определяемому Java Management Extensions.
Сделано это для стандартизации уровня сетевых управляемых ресурсов. Паттерн
проектирования интерфейса МВеап заключаются в следующем:
1. Имя интерфейса компонента МВеап должно состоять из имени класса Java,
реализующего МВеап, за которым следует суффикс МВеап.
2. В интерфейсе МВеап должны быть определены методы get и set для всех
свойств, которые необходимо сделать доступными для интерфейса управления.
3. Каждое свойство должно иметь только один метод set и один метод get в
интерфейсе управления.
4. В интерфейсе МВеап должны быть объявлены все операции, доступные
через интерфейс управления. Операции являются открытыми методами, чьи
имена не начинаются с get, is и set. Допустимы только открытые методы.
5. Возвращаемые значения и параметры для методов get и set должны быть
сериализуемыми объектами.
о поведения при
206
Глава 5
Паттерн проектирования класса МВеап выглядит тан:
1. Класс МВеап должен реализовывать соответствующий ему интерфейс МВеап.
2. Класс МВеап должен быть открытым и не быть абстрактным.
3. Класс МВеап должен иметь хотя бы один открытый конструктор.
Интерфейс PrtnterMBean (рис. 5.3) является интерфейсом МВеап для модели
принтера. Интерфейс МВеап описывает средств принтера, которыми можно
управлять. В этом примере определено только небольшое число управляемых ресурсов
принтера. Число управляемых ресурсов принтера, доступных через интерфейс
МВеап, существенно зависит от разрабатываемой системы управления. В
интерфейсе PrinterMBean определены три метода get, имена которых имеют префикс is
(строки 11-17) для булевых свойств, четыре метода get (строки 20-29) и один
метод set (строка 32). Кроме того, определены три операции: пополнение запаса
бумаги {строка 35), удаление заданий на печать из очереди {строка 38) и начало
процесса печати (строка 41).
1 // PrinterMBean.Java
2 // Данный интерфейс определяет неводы, реализуемые в классе
3 // Printer, который является компонентой МВеап.
4
5 // Пакеты Deitel
6 package com.deitel.advjhtpl.jmx.PrinterManagement;
7
8 public interface PrinterMBean (
9
10 // идет ли печать ■ данный момент?
11 public Boolean iePrintingO ,-
12
13 // включен ли принтер?
14 public Boolean ieOnline();
15
16 // яамята ли бумага?
17 public Boolean isPaperJam();
18
19 // «омрачает число листов бумаги в лотке
20 public Integer getPaperTray();
21
22 // возвращает уровень тонера в картридже
23 public Integer getToner();
24
25 // возвращает кдевтмфмкаФОр обрабатываемого задания на печать
26 public String getCurrentPrintJbb();
27
28 // возвращает массив заданий в очереди на печать
29 public String [] getPendingPrintJobs();
30
31 // устанавливает состояние доступности принтера
32 public void aetOnlinef Boolean online );
33
34 // заполнение лотка бумагой
35 public void replenishPaperTray();
36
37 // снятие заданий на печать
38 public void cancelPendingPrintJobs{);
39
Java Management Extensions (JMX) 207
40 // запуск заданий на печать
41 public void atartPrinting();
J2J
Рис. 5.Э. Определение интерфейса PrinterMBean с средствами управления принтером
Интерфейс PrinterE vent Listener (рис. 5.4) определяет методы, которые
должны быть реализованы в слушателе принтера. В интерфейсе определены три
события: отсутствие бумаги, низкий уровень тонера в картридже и замятие бумаги.
1 // PrinterEventLiatener.java
2 // Интерфейс слушателя событий принтера.
3
4 // Пакеты Deitel
5 package com.deitel.advjhtpl. jmx.Printer,-
6
7 public interface PrinterEventLiatener {
8
9 public void outO£Paper();
10
11 public void lowToner();
12
13 public void paperJam() ;
1Д 1
Рис. 5.4. Определение слушателя для обработки событий отсутствия бумаги, низкого
уровня тонера в картридже и замятия бумаги
Класс Printer (рис. 5.5) представляет собой реализацию интерфейса
PrinterMBean.
1 // Printer.java
2 // Данный класс реализует интерфейс PrinterMBean
3 // и регистрирует управляемый МВеап для программы
4 // PrinterSimulator.Java, являкщейся моделью принтера.
5
6 // Пакет Deitel
7 package com.deitel.advjhtpl.jmx.PrinterManagement;
8
9 // Базовые пакеты Java
10 import java.lang.Thread;
11 import java.util.ArrayList;
12
13 // Базовые пакеты JMX
14 import javax.management.*,-
15
16 // Пакеты Deitel
17 import com.deitel.advjhtpl.jmx.Printer.*;
18
19 public claaa Printer implements PrinterMBean,
20 PrinterEventListenex {
21
22 private Printers inulator printerSimulator;
23 private static final int PAPKR_STACK_SIZE = 50;
24 private ObjectInstance eventBroadcaaterlnatance;
25 private ObjectName eventBroadcasterName;
private ObjectHama printerНаше;
private HBeanServer mBeanServer;
public Printer()
// точка i
printer Simula tor = пен Printers lunula tor { this ) ;
Thread myThread * new Thread( printersinulator )
myThread.start();
// находим все серверы МВеап в JVM
ArrayList arrayLiat =
HBeanServerFactory.findKBeanServer{ null );
// получение ссыяхм на сервер HBeanServer
if ( arrayList.siie() = 0)
System.out.printin{ "Cannot find a HBeanServer!" );
// получение сервера HBeanServer, содержащего аарегкстриро-
// ванный сервером компонент МВеап PrinterEventBroadcaster
for ( int i = 0; i < arrayLiat.aiie(); i++ ) (
HBeanServer foundHBeanServer ■
( HBeanServer ) arrayLiat.get{ i );
// получение имени объекта для компонента
// МВеап PrinterEventBroadeaater
try (
String name я foundHBeanServer.getDefaultDomain()
+ ":type=" + "PrinterEventBroadcaster";
eventBroadcaaterHaine »= пет ObjactName ( name ) ;
>
// обработка исключений при соеданки ObjectHame
catch ( На1formedObj ееШастеЕжсер tion exception ) {
exception.printStaclcTrace{) ;
}
// проверка, зарегистрирован ли МВеап
// PrinterEventBroadcaster серверам HBeanServer
if ( foundHBeanServer.iaRegistered(
eventBroadcasterHame ) ) (
mBeanSarver = foundHBeanServer;
break;
)
) // конец цикла
) // завершение получения ссылки на HBeanServer
) // завершение конструктора PrinterSimulator
// завершение программного поток*
// принтера
public void stop()
Java Management Extensions (JMX)
105
106
107
loa
109
110
115
не
117
11B
119
120
128
129
130
131
132
133
134
135
136
137
printerSimulator.stop();
// идет ли печать?
public Boolean isPrintingO
r Boolean( printerSimulator.isPrinting{) );
// доступен ли принтер?
public Boolean isOnline{)
return printerSimulator.isOnlineO;
ли бумага?
public Boolean isPaperJamO
return printerSimulator.isPaperJam{);
// пуст ли лоток?
public Integer getPaperTray(J
return printerSimulator. get Paper Tray {) ,-
■eturn printerSimulator.getTonerO ,-
I возвращает идентификатор обрабатываемого
public String getCurrentPrintJob{)
:eturn printerSimulator.getCurrentPrintJob();
II возвращает массив заданий на печать в очереди
public String[] getPendingPrinfcJobs0
return printerSimulator.getPendingPrintJobs 0;
// задает состояние доступности принтера
public void setOnlinet Boolean online )
if ( online.booleanvalueО = true )
printerSimulator.setOnline();
else
printerSimulator.setOfflineO;
II заполнение лотка бумагой.
printerSianilator.cancelPandingPrintJoba () ,-
printerSimulator.staxtPrintingProceae();
138 public void replenishPaperTxayt)
139
140 printerSimulator.replenishPaperTray (
HI Printer.PXPER_STACK_SIZE J ;
142
143
144 // удаление заданий на печать из очереди
145 public void cancelPandingPrintJobB()
146
147
14В
149
150 // запуск процесса t
151 public void stactPrinting()
152
153
154
155
156 // передача события, связанного с бумагой, слою ЛИХ
157 protected void fireOutOfPap«rEvent()
158 (
159 // задание п&рататров и сигнатуры
160 Object[] parameter = new Object[ 1 ];
161 parameter! ° 1 = new Notification(
162 "PrinterEvent.0OT_0F_PAPER", this, 0L );
163 String[] signature « new String[ 1 [;
164 signature[ 0 ] = "javax.management.Notification";
165
166 // уведомление
167 try (
168 mBeanServer. invoice ( eventBroadcasterHame,
169 "sendNotification", perimeter, signature );
170 )
171
172 // обработка исключения при вызове метода
173 catch ( RefleetionEx caption exception ) (
174 exception.printstackTrасе О ;
175 J
176
177 // обработка исключения при взаимодействии с MBeei
178 catch ( MBeanException exception ) (
179 exception,printstackTrace0;
180 )
181
182 // обработка исключения, если MBean не найден
183 catch ( ZnstanceMotPoundException exception ) {
184 exception.printStackTrace();
1B5 )
186
187 ) // завершение метода outOfPaperSvent
188
189 // передача события отсутствия тонера слою ЛИХ
190 protected void fireI>owTonerEvent О
191 (
192 // задание параметров и сигнатуры
193 Object[] parameter = new Object[ 1 );
Java Management Extensions (JMX)
194 parameter[ 0 ] = new Notification(
195 "PrinterEvent.bOWJTONER", this, OL J;
196 String[] signature = new String[ 1 ];
197 signature[ 0 ] = ■'javax.management Notification";
19B
199 // уведомление
200 try (
201 mBeenServer.invoke( eventBroadcasterName,
202 "sendNotification", parameter, signature );
203 >
204
205 // обработка исхлшйиии ври внеове метода
206 catcli ( Reflect!овЕхcaption exception ) {
207 exception.printstackTrace(J;
206 }
209
210 // обработка исключения при вааимодействмя с MBeai
211 cetch ( MBeanException exception ) {
212 exception.printstaсkTrace();
213 J
214
215 // обработка исключения, если MBeen не вайден
216 catch ( InstanceNotPoundException exception ) (
217 exception.printStackTrace();
218 )
219
220 ) // аатершение метода lowTonerEvent
221
222 // передача события яаиятия бумаги слою JHX
223 protected void firePaperJamBvent()
224 (
225 // задание параметров и сигнатуры
226 Object[] parameter = new Object[ 1 ];
227 parameter[ 0 ] = new Notification[
228 "PrinterEvent.WlPBR_JW*", this, 0L > ;
229 String[] signature = new String[ 1 );
230 signature[ 0 ] = "javax.management.Notification";
231
232 // уведомление
233 try (
234 mBeanServer.invoke( eventBroadcaeterUa**,
235 "sendNotification", parameter, signature );
236 )
237
238 // обработка исключения ври вызове метода
239 catch( ReflectionBxception exception ) (
240 exception.printstackTrace(J;
241 )
242
243 // обработка исключения ври ыанмодействии с MBeai
244 catch( MBeanException exception ) (
245 exception.printStackTreceO;
246 )
247
248 // обработка исключения, если MBean ве найден
249 catch( instanceNotFoundException exception ) (
212
Глава 5
250 exception.printStackTraceО;
251 J
252
253 ) // ватершение метода paperJamEvent
254
255 // интерфейс реализации
256 public void outOfPaper()
257 {
258 // uisoi дадвгата
259 fireOutOfPaperEvent();
260 )
261
262 // реализация интерфейса
263 public void lowTon«r()
264 {
265 // «ыаов делегата
266 fireLowTonerEvent();
267 }
268
269 // реализация интерфейса
270 public void paperJam()
271 (
272 // «usee делегата
273 firePaparJanEventO;
274 J
275 )
Рис, 5.5, Реализация класса компонента МВеап, который определяет взаимодействие
управляющего приложения с принтером
В конструкторе Printer прежде всего осуществляется подключение к принтеру
(строки 32—34). Обычно при управлении сетевыми ресурсами подключение
осуществляется к реальному устройству. В нашем примере управляемое устройство
функционирует на том же компьютере, что и модель принтера, поэтому мы не
включаем код, осуществляющий сетевое соединение с ресурсом. В строках 36-38
осуществляется поиск всех компонентов МВеап, функционирующих под
управлением данной виртуальной машины Java. Метод findMBeanServer класса МВеал-
ServerFactory {пакет javax.management) позволяет найти ссылки на серверы
МВеап, функционирующие на данной виртуальной машине Java. Метод
findMBeanServer принимает единственный строковый параметр, который определяет
агента сервера МВеап. При передаче методу findMBeanServer значения параметра
null возвращаются ссылки на все серверы, функционирующие на данной
виртуальной машине Java.
В строках 48-72 осуществляется получение ссылки на МВеап Server, который
осуществляет передачу извещений о событиях принтера — PrinterE vent Brad-
caster (обсуждается в разделе 5.10). Компонент МВеал регистрируется с помощью
него. ObjectName однозначно идентифицирует компонент МВеап в сервере МВеап.
В строках 55-57 определен объект ObjectName для компонента МВеап Printer-
EventBroadCaster. Конструктор ObjectName принимает единственный
параметр — строку, однозначно определяющую компонент МВеап в объекте М Bean-
Server. Эта строка должна соответствовать формату: имяДомена:, за которым
следует список свойств имя=значение, в качестве разделителя используется запятая1.
Java Management Extensions (JMX) 213
ИмяДомена представляет собой строку, которая описывает расположение
одного или нескольких компонентов МВеап. Это позволяет- разделить компоненты
МВеап на категории. Например, имяДомена может быть "MyBnilding", "Boston",
"ColIegePrinters" и т.д. Эти категории могут быть определены так, что
управляемые устройства одного и того же типа, расположенные в одной и той же сети будут
включены в одну и ту же категорию имяДомена, или устройства, расположенные
в определенном месте попадут в одну категорию. Компоненты МВеап с
различными доменными именами могут быть зарегистрированы одним сервером МВеап.
В JMX имеется механизм сопоставления с шаблоном, который позволяет выделить
компоненты, соответствующие заданному доменному имени или другому
критерию поиска. Механизм сопоставления с шаблоном предполагает, что *
соответствует произвольному (включая 0) числу любых символов, ? соответствует одному
символу, остальные символы соответствуют сами себе.
Когда разбиение компонентов МВеап на категории не играет большой роли,
может быть использовано доменное имя по умолчанию, которое может быть
получено с помощью метода getDe fault Domain интерфейса MBeanServer. В строках
67-68 осуществляется проверка, зарегистрирован ли компонент МВеап Printer-
Event Broadcaster данным MBeanServer. Если это так, то цикл завершается.
В строках 86-103 осуществляется реализация методов получения булевых
значений свойств, определенных в интерфейсе PrinterMBean. В строках 104-126
осуществлена реализация остальных методов получения значений свойств компонента
МВеап. В строках 128—135 реализованы методы задания свойств компонента
МВеап. Наконец, в строках 137-154 реализованы операции, определенные в
интерфейсе МВеап. Все эти операции взаимодействуют с моделью принтера (см. рис. 5.6).
В строках 156—253 обрабатываются вызовы делегатов из реализаций методов
интерфейса PrinterEventListener (строки 255—274). Рассматриваются три вида
событий: отсутствие бумаги, низкий уровень тонера, замятие бумаги. В строках
168-169 осуществляется передача события отсутствия бумаги агенту управления,
то же для низкого уровня тонера делается в строках 201-202, а дня замятия
бумаги в строках 234-235. В каждом случае уведомление о событии осуществляется
с помощью вызова метода sendNotification компонента МВеап PrinterEvent
Broadcaster. Метод invoke сервера MBeanServer осуществляет этот вызов. Метод invoke
принимает четыре параметра: объект Object Name, однозначно определяющий
компонент МВеап, для которого вызывается метод (в данном случае компонент
МВеап Printer Event BroadCaster), строка с именем вызываемого метода
("sendNotification"), массив объектов Object, определяющий параметры, передаваемые
методу, и строковый массив, определяющий сигнатуру операции. Метод invoke
может возбудить три исключения: ReflectionException, MBeanException и Instance-
NotFoundException.
Класс Print erSimulator (рис. 5.6) моделирует принтер, к которому
подключается Printer (рис. 5.5).
1 // PrinterSimulator.Java
2 // Этот класс моделирует принтер, подключаемый к сети.
3
4 // Пакет Deitel
5 package com.deitel.advjhtpl.jmx.Printer;
Б
7 // Базовые пакеты Java
8 import java.util.Stack;
9
10 public class PrinterSimulator implements Runnable (
11
12 private Stack printerstack = new St»ck();
private boolean isOnline = true;
private boolean isPrinting = false;
private boolean isPaperJam = false;
// 50 листов ■ бумаги s лотке
private Integer paperlnTray = new Integer( 50 );
// 100% тонера (или чернил)
private Integer tonerCartridge = new Integer( 100 );
private String currentPrintJob;
private boolean i«Alive - true;
private PrinterEventListener eventListener;
// открытый конструктор no умолчанию
public PrinterSimulator(
PrinterEventListener listener )
(
eventListener = listener;
// останов ншопвения программного логова
public void stopQ
}
// Основной жизненный цикл принтера.
// Осуществляет печать ведения ив очереди.,
// 1) если принтер в автономном режиме, то окид&ние,
// 2) если в оперативною режиме, обработка задания на печать.
public void run()
t
// основной цижл в программном потоке
while ( iaAlive ) (
// пауэа, если принтер в автономном режиме
if ( tisOnline ) (
synchronized ( this ) {
// ожидание, когда принтер будет переведен
// в оперативный режим
try (
wait (J ;
)
// обработка прерывания
catch ( interruptedException exception ) (
exception.printStackTrace();
System.exit( -1 J;
)
) // завершение блока синхронизации
) // завершение if
Java Management Extensions (JMX)
// печать задания us очереди
atartPrintingProcess();
) // завершение while
public void etartPrintingProcee*()
(
// pasorpea принтера, печать первой задачи из очереди
// проверка количества бумаги в лотке и уровни тонера
try (
// раногрев принтера для печати
Thread.sleep( 1000 * 5 );
if ( ( paperlnTray.intValueO > 0 J CC
( tonerCartridge.intValuet) > 10 ) &&
( UaPaperJam ) ) (
// начало печати
currantPrintJob « getNeiitPrintJob();
i «Printing = true;
// 12 секунд ка печать документа
Thread.sleep( 1000 * 12 );
// каждое задание вклвчает в себя 10 страниц
updatePaperInTray( paperlnTray.intValueO - 10 ),-
updateToner();
updatePaperJam();
isPrinting = false;
100
101 )
102 )
103
104 // прермвание
105 catch ( InterruptedException exception ) (
106 exception.printStackTrace(J;
107 System.exit( -1 );
108 )
109
110 ) // аатершение метода etartPrintingProceeв
111
112 // возвращает задание на печать
113 public String getCurrentPrintJobО
114 (
115 return currantPrintJob,-
116 )
117
11B // принтер а оперетквнои режиме?
119 public Boolean isOnlineO
120 (
121 return пей Boolean ( iaOnlina );
122 }
123
216 Глава 5
124 // закладка бумаги в лоток принтера
125 public synchronized void updatePaperlnTxay( int newValue )
126 (
127 paperXnTray = new Integer ( newValua ) ,-
128
129 // возбуждение события, если в лотке мат бумаги
130 if ( paperXnTray.intValue() <= 0 ) (
131 eventListener.outOfPaper();
132 )
133 J
134
135 // бумага аамята?
136 public Boolean iaPaperJam()
137 (
138 return new Boolean( isPaperjam );
139 )
140
141 // удаление задания на печать на очереди
142 public void caneelPendingPrintJobs()
143 (
144 synchronized ( printerstack ) [
145 printerStack.clear();
146 }
147 )
148
149 // наполнение картриджа тонером
150 public synchronized void updat«Toner()
151 (
152
153
154
155
156
157
158
159
160 )
161
162 public synchronized void updatePaperJam()
163 (
164 if ( Hath.rendomO > 0.9 ) (
165 ia Paper Jam = true;
166 eventListener.paperjamO ;
167 }
166 }
169
170 // возвращает число листов бумаги в лотке принтера
171 public synchronized Integer getPaperTray()
172 (
173 return paperInTray;
174 J
175
176 // возвращает количество тонера в картридже
177 public synchronized Integer getTonerO
178 {
// посяе выполнения задания на печать уровень тонера
// снижается ка 1%
tonerCartridge = new Integer (
tonarCartridge.intValue() - 1 );
l/ возбуждение события при низком уровне тонера
if ( tonerCartridge.intValue() <= 10 ) (
eventLiatener.lowToner();
)
Java Management Extensions (JMX) 217
179 return t one rCart ridge ,-
180 )
181 // генерация случайного числа заданий на печать
182 // с рааличшти идентификаторами
183 public void populatePrintStackO
184 {
185 int numOfJobs = ( int ) ( Hath.random ( ) * 10 ) + 1;
186
187 // генерация заданий на печать
188 synchronized { printerstack ) (
189 for ( int i = 0 ; i < numOfJobs ; i++ ) (
190 printerStack.add ( "PRIHT_JOB_ID t" + i ) ;
191 J
192 )
193 }
194
195 // возвращает следующее задание ■ очереди и пополняет
196 // очередь, если ока пуста
197 public String getNextPrintJobQ
198 {
199 if ( printerStack.isEmptyO ) (
200 populatePrintStack ( );
201
202 // моделирование отсутствия заданий на печать
203 try {
204 Thread.sleep (
205 ( int J ( Math.random() * 1000 * 10 ) );
206 )
207
208 // прерывание
209 catch ( Inter niptedExcaption exception ) (
210 exception.printStackTrace(J ;
211 System.exit ( -1 ) ;
212 )
213 >
214
215 //удаление первого ресурса в очереди
216 String printJob;
217
218 synchronized ( printerStack ) (
219 printJob = ( String J printerStack.popO;
220 )
221
222 return printJob;
223
224 ) // затеряение метода getMextPrintJob
225
226 // аоавращает аса задания, которое не были еже напечатали
227 public String[] getPendingPrintJobs()
228 {
229 String[] pendingPrintJobs;
230
231 // создание насснва заданий на печать
232 synchronized ( printerStack ) (
233 Object[] temp = printerStack.toArray(J ;
234 pendingPrintJobs = пей String[ temp-length ] ;
235
236 for ( int i » 0 ; i < pendingPrintJobs.length ; i++ ) (
237 pendingPrintJobs [ i ] = [ String )tempt i 1;
238 )
239 )
240
241 return pendingPrintJobs;
242
243 ) // заверяете метода get PendingPrintJobs
244
245 // перевод принтера ■ оперативный ражим
246 public void setOnline()
247 (
248 isOnlina = true;
249
250 // уведомление всех ожидающих программных потоков
251 synchronised ( this ) (
252 notifyAll() ;
253 )
254 }
255
256 // перевод принтера в автономный режим
257 public void «etOfflineO
258 {
259 isOnline = false;
260 )
261
262 // заполнение яотха принтера бумагой до
263 // заданного значения
264 public void replenishPaperTzay ( int paparStack )
265 (
266 updatePaperlnTray[ paperStack ) ;
267 }
26B
269 //печатает ни принтер?
270 public boolean isPrintingt)
271 [
272 return isPrinting;
273 )
274 )
Рис. 5.6. Класс, моделирующий принтер, может возбуждать три типа событий
Конструктор класса Printersimulator (строки 28—32) принимает в качестве
параметра слушатель Printer Event Listener, которому Printer Simulator передает
события. В данном примере PrinterEventListener является шлюзом, через который
PrinterSimolater аередает события компоненту МВеап Printer. Как уже
говорилось ранее, в реальных ситуациях используются сетевые протоколы, например,
SNMP или HTTP для обеспечения взаимодействия между управляемым ресурсом
в сети и соответствующим компонентом МВеап. Метод stop (строки 35-38)
переводит принтер в автономный режим.
Метод пш (строки 44-72) определяет жизненный цикл PrinterSimnlator,
который выполняется в программном потоке (объект Thread). Если принтер находится
в оперативном режиме, метод пш вызывает метод startPrintingProcess (строки
74-110) для моделирования работы принтера.
Java Management Extensions (JMX) 219
Метод startPrintingProcess в течение пяти секунд «разогревает* принтер. Бели
лоток с бумагой не пуст, уровень тонера не слишком низок, а бумага не замята,
принтер начинает печатать очередное задание из очереди. В строке 91
предполагается, что печать любого задания занимает двенадцать секунд. В строках 94-96
осуществляется вызов методов, осуществляющих изменение значений свойств модели
принтера при печати задания.
Метод getCorrent Print Job (строки 113-116) возвращает текущее задание на
печать. Метод isOnline (строки 119-122) возвращает true, если принтер находится
в оперативном режиме. Метод updatePaperTray (строки 125-133) осуществляет
«заполнение» лотка принтера бумагой. Если в лотке нет бумаги, то возбуждается
событие out-of-paper. Метод isPaperJam (строки 136-139) возвращает true, если
бумага замята. Метод Сап celPendlngPrin (Jobs (строки 142-147) удаляет задания
на печать из очереди.
Метод updateToner (строки 150-160) осуществляет «заполнение» картриджа
тонером. Прн слишком низком уровне тонера в картридже возбуждается событие
low-toner. Метод updatePaperJam (строки 162-168) возбуждает событие paper-
jam при замятии бумаги.
Метод getPaperTray (строки 171-174) возвращает число листов бумаги в лотке
принтера. Метод getToner (строки 177-180) возвращает уровень тонера в
картридже. Метод populatePrintStack (строки 183-193) генерирует задания на печать.
Метод getNextPrintJob (строки 197-224) возвращает следующее задание на
печать и генерирует новые задания на печать, если очередь пуста. Метод getPending-
PrintJobs (строки 227-248) возвращает список заданий на печать. Метод setOnline
(строки 246-254) переводит принтер в оперативный режим. Метод set Offline
(строки 257-260) переводит принтер в автономный режим. Метод replenish-
РарегТгау (строки 264-267) осуществляет «заполнение- отка принтера
заданным числом листов бумаги. Метод is Printing (строки 270 273) возвращает true,
если принтер осуществляет вывод па печать.
5.3.2. Реализация агента управления JMX
Агент управления JMX связывает компоненты МВеап с управляющим
приложением. Обычно агент JMX содержит сервер МВеап, набор компонентов МВеап,
который представляет управляемые ресурсы и по крайней мере один адаптер
протокола или соединитель — компоненты МВеап, позволяющие удаленному
управляющему приложению получать доступ к MBeanServer. Экземпляр сервера
MBeanServer действует в качестве реестра для всех компонентов МВеап. Компоненты
МВеап, представляющие управляемые ресурсы, управляются управляющим
приложением через сервер МВеап. На рис. 5.7 представлена архитектура агента JMX.
Компоненты МВеап представляют управляемые ресурсы или сервисы
управления; они регистрируются сервером МВеап. Адаптер протокола или соединитель
является шлюзом, который позволяет удаленному управляющему приложению
взаимодействовать с зарегистрированными на сервере МВеап компонентами МВеап. Так
как соединители являются компонентами МВеап, а компоненты МВеап могут
взаимодействовать друг с другом через сервер МВеап, то архитектура агентов
управления JMX позволяет локальным или удаленным управляющим приложениям
получать доступ к свойствам компонентов МВеап и открытым операциям. Локальные
управляющие приложения могут обращаться к компонентам МВеап напрямую
через сервер МВеап; удаленные управляющие приложения должны использовать
адаптер протокола или соединитель.
Приложение Printer Management Agent (рис. 5.8) представляет собой агент
управления JMX. PrinterManagementAgent создает сервер MBeanServer и
запускает основанный на RMI сервис МВеап, компонент МВеап Printer Event Broad-
220
Глава 5
(Адаптер протокола
или соединитель
Сервер МВеап
Рис. 5.7. Архитектура агента JMX
caster, который передает уведомления принтера, а также запускает компонент
МВеап Printer, который является связующим звеном между Printer Simulator
ii сервером МВеап. Метод create МВеап Server класса МВеап Server Factory создает
новый сервер МВеап. В строках 20-21 создается сервер МВеап с умалчиваемым
доменным именем, обеспечиваемым МВеап Server Factory. В строках 29-30
создается RMlConnectorServer с помощью вызова метода createMBean интерфейса МВе-
anServer. Метод createMBean интерфейса МВеап Server принимает два
параметра: строку, определяющую имя класса, экземпляр которого необходимо создать,
и объект ObjectName, определяющий имя компонента МВеап. Строка,
определяющая имя класса, не может быть nail. Если ObjectName равен null, то компонент
МВеап RMlConnectorServer, отвечающий за обработку удаленных соединений
RMI, может получить имя автоматически. В строках 33-40 осуществляется
создание экземпляра и регистрация компонента МВеап Printer Event Broadcaster
сервером МВеап. В строках 43-50 осуществляется создание и регистрация компонента
МВеап Printer. В строках 56-77 осуществляется перехват исключения, которое
может произойти при создании компонента МВеап и определении имен объектов
компонентов МВеап. В строках 88-90 вызывается метод setPort компонента
МВеап RMlConnectorServer для задания номера порта 5555 для соединителя RMI.
В строках 91-93 вызывается метод start компонента МВеап RMlConnectorServer
для запуска соединителя. В строках 97-109 осуществляется перехват
исключений, которые могут случиться при вызове методов компонента МВеап.
1 // PrinterHanagementAgent■Java
2 II Данное приложеиле создает сервер MBeanServer и запускает
3 // соединитель RHI сервиса МВеап.
4
5 // Пакет Deitel
6 package com.deitel.advjhtpl.jmx.PrinterManagemant;
7
8 II Базовые пакет JMX
9 import javax.management.*;
10
11 public class PrinterManagementAgent {
12
13 public static void main( String1] args )
14 (
15 Objectlnstance rmiConnectorServer => null;
)
Java Management Extensions {JMX) 221
16 ObjectInstance printer = null;
17 ObjectInstance broadcaster = null;
18 ObjectHame objectHame = null;
19
20 // создание сервера МВеапServeг
21 МВеапServer server =
22 MBeanServerFactory.createMBeanServer();
23
24 // создаиле сервера соединителя RMI, компонента МВеап подели
25 // принтера и компонента МВаап для передачи уведомлений
26 try (
27
28 // создание сервера соединителя RMI
29 rmiConnectorServer = server.сreateMBean (
30 "com.aun.jdmk.comm.RmlConnectorServer", null );
31
32 // создание компонента МВеап для передачи уведомлений
33 String name = server.getDefaultDomain()
34 + ":type=" + "PrinterEventBroadcaater";
35 String clasaNarae = "com.deitel.advjhtpl.jmx."
36 + "printerManagament.PcinterEventBroadcaster";
37
38 objectHame = new ObjectHame( name );
39 printer = server.createMBean(
40 className, objectHame );
41
42 // создание компонента МВеап модели принтера
43 name = server.getDefaultDomain()
44 + •":type=" + "Printer";
45 className = "com.deitel.advjhtpl.jmx."
46 + "PrintarHanagemant. Printer",-
47
48 objectHame = new ObjectHame( name );
49 broadcaster = server.createMBean(
50 className, objectHame );
51
52 } // завершение блока try
53
54 // обработка исключений, не связанных с JMX
55 catob ( NotContpliantMBeanException exception ) {
56 exception.printStackTr&ce() ,-
57 )
58
59 // обработка исключений конструктора МВеап
60 catch ( MBeanException exception ) {
61 exception.printStackTracef);
62 )
63
64 // обработка исключения в ситуации, когда МВеап уже существует
65 catch ( InstanceAlreadyExist«Exception exception ) {
66 exception.printStackTrace();
67 )
68
69 // обработка исключений конструктора МВеап
70 catch ( ReflectionException exception ) (
71 exception.printSteckTrace();
74 // обработка капрааильвого имени объекта
75 catch ( MalfonnedObjectNameException exception) (
7 6 exception.printBtackTrace();
77 )
78
79 // заданна номера порта
80 Object[1 parameter s new Object [ 1 ),-
81 parameter[ 0 ] = new Integer( 5555 );
82 String[1 signature = new String! 1 );
83 signature [ 0 1 = "inf;
84
85 // ameoB метода eetPort лая RmiConneetoeServer,
86 // запуск сервиса соединителя RHI
87 try {
88 server.invoke(
89 rmiConnectocServer.getObjectNameO , "ееWort"
90 parameter, signature ),-
91 server.invoke(
92 xmiConnectorServer.getObjectNameO, "start" ,
93 new Qbject[ 0 ], new String[ 0 ] );
94 )
95
96 // обработка исключения при выполнении метода
97 catch ( RefleetionBxception exception ) (
98 exception.printStackTracef);
99 )
100
101 // обработка исключения при ■ваимодейстаки с МВеап
102 catch ( MBeanZxception exception ) (
103 exception.printStackTracef);
104 }
105
106 // обработка исключения, асяи МВеап к* найден
107 catch ( InntanceRotPoundException exception ) {
108 exception.printStackTrace();
109 )
110
111 )
112 )
Рис. 5.8. Создание и запуск агента управления JMX
5.3.3. Рассылка и получение уведомлений
Б данном разделе объясняется, как управляемые ресурсы или устройства рас-
сылают широковещательные уведомления через компонент МВеап рассылки
уведомлений, а также как удаленное управляющее приложение получает
уведомления от компонента рассылки. Компонент рассылки представляет собой МВеап,
в котором определено одно или несколько событий. Он имеет возможность
рассылать уведомления всем зарегистрированным слушателям, как только эти события
будут получены от управляемых ресурсов. Коипонеит МВеап рассылки
уведомлений о событиях должен реализовывать интерфейс NotificationBroadcaster для
объявления себя источником уведомлений. Для получения уведомлений управ-
Java Management Extensions (JMX) 223
ляющее приложение должно соединиться с компонентом рассылки уведомлений.
В нашем случае управляемым ресурсом является компонент МВеап Printer, а
компонентом рассылки уведомлений является PrinterEventBroadcaster,
зарегистрированный сервером созданным в классе PrinterManagementAgent (рис. 5.8).
Интерфейс PrinterEventBroadcasterMBean (рис. 5.9) представляет собой
интерфейс для компонента МВеап рассылки уведомлений принтера. Этот интерфейс
определяет единственную операцию — send Notification. Компонент рассылки
уведомлений также является МВеап, так что его нужно зарегистрировать на сервере
МВеап Server.
1 // PrinterEventBroadcaaterMBean.Java
2 // Этот класс определяет клтерфейс МВеап.
3
4 // Пакет Deitel
5 package сов.deitel.advjhtpl.jnut.PrinterManagemant;
6
7 // Базовые пакеты JMX
8 Import javax.management.Notification;
9
10 public interface PrinterEventBroadcasterMBean (
11
12 public void sendHotification( Notification notification );
13 )
Рис. 5.9. Интерфейс компонента МВеап рассылки уведомлений
Класс PrinterEventBroadcaster (рис. 5.10) является стандартным компонентом
МВеап, который реализует интерфейс PrinterEventBroadcasterMBean.
PrinterEventBroadcaster рассылает уведомления о событиях принтера зарегистрирован-
ным слушателям. Он расширяет функциональность класса
javax.management.Notification Broadcaster Support (строка 15), который обеспечивает в частности
регистрацию слушателей. В строках 13-23 определяются три тина уведомлений.
PrinterEventBroadcaster переопределяет метод getNotificationlnfo, чтобы
возвращать массив объектов MBeanNotiflcationlnfo (пакет javax.management),
содержащий имя класса уведомлений и тип разосланного уведомления. В строках 33-39
определяется массив строк, содержащий типы уведомлений. В строке 42
указывается имя класса уведомлений. В строках 45-46 создается строка, в которой
описываются уведомления, которые может разослать компонент МВеап
PrinterEventBroadcaster. Конструктор MBeanNotiflcationlnfo (строки 49-50) принимают три
параметра; массив строк, в которых указываются типы уведомлений, строка
с именем класса уведомлений и строка, содержащая описание уведомлений.
В строке 52 возвращается массив MBeanNotiflcationlnfo.
1// PrinterEventBroadcaater.java
2 // Этот класс определяет компонент МВеап, который
3 // обеспечивает информацию о событиях.
4
5 // Пакет Deitel
6 package сов.deitel.advjhtpl. j^lUt.PrinterManag^■ant,-
7
в // Базовые пакета JMX
9 import javax.management.MBeanNotificationlnfо;
10 inport Java».management.NotificationBroadcasterSuppcrt;
11
12 // расширение классе NotificationBroadcasterSupport
14 public class PrinterEventBroadcaster
15 extends NotificationBroadcasterSupport
16 implements PrinterEventBroadcasterMBean (
17
18 private static final String ODT_OF_PAPER =
19 "PrinterEvent.OUT_OF_PAPER";
20 private static final'string LOH_TOHER =
21 "PrinterEvent.LOW_TONER";
22 private static final String PAPER_JAM =
23 "PrinterEvent.PAPER_JAM";
24
25 // илформация о событиях
26 public MBeanNotificationInfo[] getNotificationlnfo()
27 (
28 // массив, содержащий описатели объектов
29 MBeanHotificationlnfo[] descriptorArray =
30 new MBeanHotificationlnfo[ 1 ];
31
32 // рааличкые rnrnai событий
33 String[] notificationTypes = new String[ 3 ];
34 notificationTypes[ 0 ] =
35 PrinterEventBroadcaster.OOT_OF_PAPER;
36 notificationTypes[ 1 ] =
37 PrinterEventBroadcaster.LOW_TONER;
38 notificationTypes[ 2 ] =
39 PrinterEventBroadcaster.PAPER_JAM;
40
41 // тип класса уведомления
42 String classType = " javax.managwint.Hotif ication";
43
44 // описание MBeanHotificationlnfo
45 String description =
46 "Notification types for PrinterEventBroadcaster";
47
48 // sвполнекие массива описателей
49 descxiptorArrayt 0 ] = пей MBeanHotificationlnfo(
50 notificationTypes, classType, description );
51
52 return descriptorArray;
53
54 } // завершение метода getNotificationlnfo
55 }
Рис. 5.10. Реализация компонента MSean рассылки уведомлений о событиях принтера
Класс PrinterEventHandler (рис. 5.11) получает уведомления о событиях от
компонента МВеап рассылки уведомлений. В строках 24-53 определяется анонимный
внутренний класс, реализующий интерфейс слушателя уведомлений (]аvax.manage-
ment.NotificationListener). Реализация метода handleNotification (строки 26-51)
обрабатывает входящие события. В строках 33-37 осуществляется обработка события
отсутствия бумаги. В строках 39-43 обрабатывается низкий уровень тонера в
картридже. В строках 35-49 обрабатывается замятие бумаги. В строке 64 в
конструкторе PrinterEvent Handler (строки 56-86) задается режим рассылки уведомлений —
C!ieiitNotificationHandler.PUSH_MODE (пакет conLSun.jdiuk.comm). В режиме
чрогалкивания, как только сервер соединителя RMI получает уведомление, он не-
Java Management Extensions (JMX)
медленно передает уведомление клиенту соединителя RMI. В строках 63-73
осуществляется добавление слушателя к компоненту МВеап рассылки уведомлений.
В строках 66-70 указывается имя объекта компонента МВеап рассылки
уведомлений. Метод addNotificationListener (строки 72-73) принимает четыре параметра:
Object Name, определяющий имя компонента рассылки уведомлений, под которым
он будет зарегистрирован слушателем уведомлений, NotificatioaListener, который
обрабатывает уведомления, разосланные компонентом рассылки уведомлений,
класс, который реализует интерфейс NotificationListener, осуществляющий
фильтрацию уведомлений, и объект Object, который содержит контекст, который должен
быть передан слушателю. В нашем примере NotificationListener, определенный
в строках 24-53, регистрируется компонентом МВеап PrinterEventBroadcaster,
который связывается с сервером МВеап, В строках 76-84 осуществляется перехват
исключений, которые могут возникнуть при добавлении слушаталя. Метод handle-
OotOfPaperEvent (строки 89-92) делегирует событие отсутствия бумаги. Метод
handleLowTonerEvent (строк 95-98) делегирует событие низкого уровня тонера
в картридже. Метод handlePaperJamEvent (строки 101-104) делегирует событие
замятия бумаги.
1 // PrinterEventBandler.Java
2 // Класс добавляет слушатель к компоненту МВеап,
3 // рассылающему уведомления, определяет обработчики событий.
4
5 // Пакет Deitel
6 package com.deitel.advjhtpl.jmx.Client;
7
S // Базовые пакеты JHX
9 import javax.management.*;
10
11 // Базовые пакеты JSHK
12 import com.sun.jdmk.<
13 import com.sun.jdmk.<
14
15 // Пакеты Deitel
16 import com.deitel.advjhtpl.jmx.Printer.*;
17
18 public class PrinterEvantHandler (
19
20 private RmiConnectorClient rraiClient;
21 private PrinterEventListener eventTarget;
22
23 // анонимный внутренний класс слушателя уведомлений
24 private NotificationListener notificationLietener =
25 new HotificationListenerO <
26 public void handleNotification(
27 Notification notification. Object handback )
28 {
// i
String notificationType = notification.getType();
// обработка pasjm*B*ac типов уведоиненмй
if ( notificationType.equals(
"PrinterEvent.OOT_OF_PAP£R" ) ) {
handleOutOfPaperEvent О;
if < notificationType.equals!
-printerEvent-LOH^TOm»'' ) ) f
handleLowTonerEvent();
)
if < notificationType.equals(
"PrinterEvent.PAPER_JAM" ) ) (
handle?aperJainEvent{) ;
return;
)
) // вавариевме метода handleNotification
); // завершение анонимного внутреннего класса
// конструктор по умолчанию
public PrinterEventHandler(
RmiConnectorClient inputBniClient,
PrinterEventLiatener inputEventTarget )
(
rmiClient = inputBmiCliant;
eventTaxget = inputEventTarget;
// задание режима проталкивания для уведомлений
rmiClient.setModef ClientBotificationHandler.POSH_MODE );
// регистрация слушателя
try (
ObjectHame objactHame = new ObjectNarae(
rmiClient-getDafaultDoraain()
+ ":type=" + "PrinterEventBroadcaster" );
rmiClient.addNotificationListener( objectHame,
notificationListener, null, null ) ;
// если HBean отсутствует а сервере HBean
catch ( InstanceNotFoundException «Rception) (
•Rception.printStackTracaO ;
// некорректный формат имажм объекта
catch ( HalfornedObjectHaneException enception ) (
exception.printStackTrace();
конструктора PrinterEventHandler
// делегирование события отсутствия бумага
private void handleOutOfPaperEvent{)
(
•v«ntTarget.outOfPaper();
// делегирования события низкого уровни тонера
private void handleLowTonerEvent()
Java Management Extensions (JMX)
227
96 (
97 eventTarget.lotrToner();
98 )
99
100 // делегирование со бит™» импмя бумаги
101 private void handle Pape r JamEvent ()
102 (
103 «vantTarget.paperJam();
104 )
105 )
Рис. 5,11. Получение уведомления о событии от сервера М8еап и обработка событий,
специфичных для принтера
5.3.4. Управляющее приложение
Управляющее приложение в нашем примере обладает простым графическим
интерфейсом пользователя для управления принтером. Класс ClientPrinterManage-
roent (рис. 6.12) устанавливает соединение с сервером МВеап с помощью RmiCon>
nectorClient (пакет coro.snn.jdmk.comm). Управление принтером осуществляется
сервером МВеап. Конструктор ClientPrtnterManagement (строки 24-29) создает
RnuCounectorCIieitt, соответствующий RmiConnectorServer, который связав с
сервером MBeanServer, созданным агентом PrinterManagementAgent (рис. 6.8).
В строке 27 создается RmiConnectorClient. В строках 30-31 осуществляется
получение адреса клиента соединителя. В строке 34 указывается номер порта клиента со-
единитаяя. Номер порта клиента соединителя должен быть согласован с сервером
соеднинтеля. В строке 37 осуществляется соединение с удаленным MBeanServer.
Метод getClient (строки 42-45) возвращает ссылку на RmiConnectorClient. В
методе main в строках 57-68 осуществляется запуск графического интерфейса
пользователя управляющего приложения. В строках 61-63 осуществляется отображение
окна (представлено на рис. 5.14-5.16).
1 // ClientPrinterManagament.Java
2 // даянсе приложение устанавливает сседихекке с MBeanServer
3 // и совдает МВеап для PrinterSimulator.
4
5 // Пакет Deitel
6 package com.deitel.advjhtpl. jinx.Client;
7
8 // Baaonwe пакеты Java
9 import java.awt.*,-
10 lay ort java.awt.event.*;
11
12 // Бааовый пакет JMX
13 import javax.management.*;
14
15 // Eaaomue nuwni JDKX
16 import com,aun.jdmk.coma.RmiConnectorClient;
17 import ссш.аип. jdmk.comm.RmiConnectorAddreaa.-
18
19 public class CliantPrinterManagament (
20
21 private RmiConnectorClient rmiClient;
22
23 // совдакие соединения
public ClientPrinterManagement()
// создание эгаенпляра адреса
BmiConnectorAddress rmiAddress =
new RmiConnectorAddreas();
// указание порта
rmiAddreaa.eetPortt 5555 );
// установление ссединекия
rmiClient.connect! rmiAddress );
} // завершение конструктора ClientPrinterManagement
// возвращает ссыпку кя RmiConnectorClient
public RmiConnectorClient getClientf)
(
return rmiClient;
)
public stetic void main( String[] args )
(
// соединение на стороне клиента
ClientPrinterManagement clientHanager =
new ClientPrinterManagement();
// получение дескриптора RMIConnectorClient
BmiConnectorClient client * clientManager.getClient();
// запуск графического пользовательского интерфейса
PrinterManagementGUX printerManagementGOT =
new PrinterManagementGDi ( client );
// отображение
printerManagementGDX.setsize(
new Dimension( 500, 500 ) );
printerManagementGOl. set Visible ( true ) ;
} // заяержекие метода main
Класс PrmterManagementGUI (рис. 5.13) определяет графический
пользовательский интерфейс для управляющего приложения. Оно содержит панель для
отображения состояния принтера и кнопки для обновления состояния, добавления
бумаги и удаления заданий из очереди.
1// PrinterManagementGOl.Java
2 // Данный класс определяет графический пользова-
3 // интерфейс для приложения управления прилтером
Java Management Extensions (JMX)
5 // Паке* Deitel
6 package com.deitel.advjhtpl.jmx.Client;
7
8 // Базовые пакеты Java AWT
9 import java.awt.*; '
10 import java.awt.event.*;
11
12 // Стандартные расширения Java
13 import javax.swing.*;
14
15 // Базовые пакеты JMX
16 import javax.management.*;
17
18 // Базовые пакеты JDMX
19 import com.sun.jdmk.comm.RmiConnectorClient;
20 import com.sun. jdmk..comm.HmiConnoctorAddress;
21
22 // Пакеты Deitel
23 import com.deitel.advjhtpl.jmx.Printer.*;
24
25 public class PrinterManagamentGUI extends JFrame
26 implements PrinterEventListener {
27
28 // TextAppender добавляет текст ■ JTextArea. Этот Runnable
29 // объект должен быть выполнен с помощью методов утилит
30 // Swing invokeLater или InvokaAndH&it, так как он
31 // модифицирует компонент Swing.
32 private class TextAppender implements Runnable {
33
34 private String text;
35 private JTextArea textArea;
36
37 // конструктор TextAppender
38 public TextAppender) JTextArea area. String newText )
// отображение нового текста в JTextArea
public void run()
i
// добавление нового сообщения
textArea.append( text };
// перемещение курсора в конец messageArea,
// чтобы сообщение стало видимым на экране
textArea.setCaretPosition(
textArea.getTextO .length() ) ,-
} // завершение внутреннего класса TextAppender
private ObjectName objectName;
private RmiConnactorClient client;
private JTextArea printerStatusTextArea = new JTextArea(};
230
Глава 5
61 private JTaxtAxea printerEventTextArea = new JTextAxea();
62
63 public PrinterManagamentGUX{ RnkiConnectorClient xmiClient )
64 {
65 super( "JMX Printer Management Example" );
66
67 Container container = getContentPana();
69
69 // панель состояния
70 JPanel printexStatusPanel = new JPaneH) •"
71 printerstatusPanel.setPreferredSize(
72 new Dimension< 512, 200 ) );
73 JScrollPana statusScrollPane = new JScrollPane();
74 statusScrollPane.eetAutoecrolls( true );
75 statusScrollPane.satPreferredSise(
76 new Dimension( «00, 150 ) );
77 statusScrollPane.getviewportj).add(
78 printerstatusTextAxea, null );
79 printerStatusPanel.add< statusScrollPane, null );
80
81 // павел* с кнопками
82 JPanel buttonPanal = new JPanel();
83 buttonPanel.setPreferredSize(
84 new Dimension{ 512, 200 ) );
B5
86 // опредехение действия дяя кнопки проверки состояния
67 JButton checkstatusButton =
88 new JButton( "Check Status" );
89 checkstatusButton.addActionListener(
90
91 new ActionListener() (
92
93 public void actionPerformed( ActionEvent event ) (
94 checkStatusButtonAction( event );
95 )
96 )
97 );
98
99 // определение действкн для добавления бумахм
100 JButton addPaperButton = new JButton( "Add Paper" );
101 addPaperButton.addActionListener(
102 new ActionListener() (
103
104 public void actionPerformed(ActionBvent event) (
105 addPaperButtonAction( event );
106 )
107 )
108 ) ;
109
110 // опредехение действии удаления заданий ив очереди
111 JButton cancelPendingPrintJobsButton = new JButton(
112 "Cancel Pending Print Jobs" );
113 cancelPendingPrintJobsButton.addActionListener(
114 new ActionListener() (
115
116 public void actionPerfoxned( ActionEvent event ) {
Java Management Extensions (JMX)
231
117 cancelPendingPrinttJobaButtonAction ( event ) ;
118 )
119 )
120 ) ;
121
122 // добавление трах кнопок на панель
123 buttonPanel.add( checkstatuaButton, null ) ;
124 buttonPanal.add( addPaparButton, null );
125 buttonPanal.add( cancelPendingPriO'tJobsButtoA, null );
126
127 // панель событий
128 JPanal printerEventPanel = new JPanal();
129 printerEventpanel.setPrererredSiEe(
130 new Dimension( 512, 200) );
131 JSего11Pane eventaScrollPane = new JScrollPane();
132 eventaScrollPane.aetAutoacrolls( true ) ;
133 eventsScrollPane.setPreferredSize(
134 пей Dimension ( 400, 150 ) ) ,-
135 evantaScrollPane.getViewport().add(
136 printerEventTextAxea, null );
137 printerEventPanel.add( eventaScrollPane, null );
138
139 // иннциалияаоия текста
140 printerStatusTextAxaa.eetTaxt( "Printer Statu*: \nM ) ;
141 printerEventTextAxea.aetText ( "Events: \a" );
142
143 // сборка овладей
144 container.add( printerStatusPanel, BordarLayout.NORTH );
145 container.add( printer!vantPana1, BordarLayout,SOOTH );
146 container.add( buttonPanal, BordarLayout.CENTER );
147
148 // аадакме ссылки на RmiConnaetorCliant
149 client ■ rmiClient;
150
151 // uuoi метода e tart Printing
152 // компонента KBean PrintarSimulator
153 try (
154 String паям ■ client.getDefaultDomain()
155 + ":type-- + "Printer";
156 objactMama — new OojectName( паве );
157 client.invoke( objectName, "a tartPrinting",
158 new C4>ject( 0 ], new String£ 0 ] );
159 )
160
161 // некорректно* имя объекта
162 catch ( MalxormadObjectNameException exception ) (
163 exception.printStackTrace() ;
164 )
165
166 // если нальая шпик метод
167 catch ( ReflectionException exception) (
168 exception.printstackTrace();
169 )
170
171 // если при вызове метода возникло исключение
172 catch ( KBe&nException exception ) (
173 exception. pnntStackTraceO;
174 )
175
176 // если MBean не зарегистрирован сервером MBean
177 catch ( InstanceHotFoundException exception ) (
178 exception.printStackT raced:
179 )
180
181 // создание экземпляра PrinterEventHotifier
182 PrinterEventHandler printerEventHandler =
183 new PrinterEventHandler( client, this >;
184
185 // удалекне регистрации MBean при эахрнтни окна
186 addWindowLietener(
187 new WindowAdapterO {
188 public void windowC losing/ ( HindowEvent event )
189 (
190 // удалекне регистрации MBean
191 try (
192
193 // удаление регистрации PrinterSimulator
194 client.unregisterHBean( objectHame );
195
196 // удаление регистрации
197 // PrinterEventBroadcaster
198 String name = client.getDefaultDomain()
199 + ":type="' + "PrinterBventBroadcaster" ;
200 objectHame = new Obj*ctH*me( name ),-
201 client.unregisterHBean( objectHame );
202 )
203
204 // некорректное кия объекта
205 catch { HalfocroedObjectName&xception exception)
206 exception.printStackTracef);
207 )
208
209 // перехват исклвхекня
210 catch ( MBeanRegistrationException exception )
211 exception.printStac)cTrace() ;
212 )
213
214 // если MBean не зарегистрирован сервером МВеап
215 catch ( Inst&nceHotFoundException exception ) (
216 exception.printStackTrace();
217 }
218
219 // завершение программы
220 System.exit( 0 );
221
222 ) // завершение метода windowClosing
223
224 ) // завершение конструктора HindowAdapter
225
226 ); // зехершение addHindowListener
227
228 } // завершение конструктора PrinterManagementGUX
Java Management Extensions (J MX)
229
230 // событие отсутствия бумаги
231 public void outOfPaper()
232 {
233 SwingUtilitiea.invokeLater(
234 new TextAppender( printerEventTextAxea,
235 "\nEVENT: Out of Paper'\n" ) )
236 )
237
238 // отсутствие тонера
239 public void lowToner()
240 {
241 SWingDtilities.invokeLater(
242 new TextAppender( printerEventTextArea,
243 "\nEVENT: Toner Low!\n" ) ) ;
244 }
245
246 // замятие бумага
24*7 public void paper Jam ()
248 (
249 SwingOtilities.invokeLater(
250 new TextAppender( printerEventTextAeea,
251 "\nEVENTr Paper Jam'\n" ) );
252 )
253
254 // добавление бумага в лоток
255 public void addPapecButtonAction( ActionEvent event )
256 i
257 try (
258 client.invoke( objectHame, "replenishPaperTray",
259 new Object{ 0 ], new String[ 0 ] ) ;
260 }
261
262 // если нельзя вызвать метод
263 catch ( ReflectionException exception)
264 (
265 exception.printstackTrace();
266 )
267
268 // если вызванный метод обусловил исключение
269 catch ( MBeanException exception ) (
270 exception.printsteckTrace();
271 )
272
273 // если КВеап не зарегистрирован сервером ИВеап
274 catch ( instancetlotFoundException exception ] {
275 exception.printStackTrace();
276 )
277
278 ) // завершение метода addPaperButtonAction
279
280 // удаление заданий на печать иэ очереди
281 public void cancelPendingPrint<TobsButtonAction(
282 ActionEvent event )
283
284
285 client, invoke ( oejectHame, "cancelPendingPrinbJoes" ,
286 new Object[ 0 ], new String[ 0 ] );
287 )
288
289 // если ильм выжватв метод
290 catch ( ReflectionException exception)
291 (
292 exception.printStackTrace() ;
293 }
294
295 // если выаааштй метод обусловил искнкченне
296 catch ( MBeanExeeption exception ) {
297 exception.printStackTracaO;
298 )
299
300 // если HBean не аарегистрмрован сервером КВеап
301 cntcb ( InatanceNotFoundKxception exception ) (
302 exception.printstackTrace();
303 )
304
305 ) // аавержемме метода cancelPendingPrintJobaButtonAction
306
307 public void checkstatuaButtonAction( Actionsvent event )
308 (
309 Object onlineReaponsa = null;
310 Object paperjanReaponse = null;
311 Object printingReaponse = null;
312 Object paperTrayResponse = null;
313 Object pendingPrinbJohsResponse = null;
314
315 // удаленное управление принтером
316 try (
317
318 // проверка состояния принтера
319 onlineReeponee = client.invoice( objectKaea,
320 "lsOnline", new Object{ 0 ], new String[ 0 ] );
321
322 // проверка замятия бужам» принтером
323 paperJaBReaponaa = client.invoke( objectHame,
324 "iaPaperJam", new Object[ 0 ], new String[ 0 ] );
325
326 // проверка, печатает ни принтер
327 printingReaponse » client.invoke( objects***,
328 "imprinting", new Object! 0 ], new String{ 0 ] );
329
330 // получаю» информации о бумага в нотке
331 paperTrayReBponse » client.invoke( objectName,
332 "getPaperTray", new Object[ 0 ], new String[ 0 ] );
333
334 //
335 pendingPrintJobsReaponse » client.invoke ( objectHame,
336 "getPendingPrintJobs" , new Object[ 0 ],
337 new String[ 0 ] );
338 )
339
340 // ec
Java Management Extensions (JMX) 235
341 catch ( ReflectionException exception } {
342 exception.printStackTrace();
343 )
344
345 // если вызванной метод обусловил исклвченне
346 catch ( MBeanException exception ) (
347 exception.printStackTrace();
348 )
349
350 // если KBean не зарегистрирован сервером КВеал
351 catch ( InstanceNotFoundException exception ) (
352 exception.printStackTrace));
353 )
354
355 // состояние принтера
356 boolean i«Online =
357 ( ( Boolean ) onlineResponse ).booleanValue ();
358
359 // отображение состояния
360 if ( iaOnline ) {
361 SwingUtilities.invokeLater( new TextAppender(
362 printerStatusTextAxea,
363 "\nPrinter is ONLINE An" ) );
364 ]
365 elae (
366 SwingOtilities.invokeLater( new TextAppender(
367 printerStatusTextAxea,
368 "\nPrinter is OFFLINE.\n" ) ) ,'
369 )
370
371 // ааиитие бума™
372 boolean isPaperJam =
373 ( ( Boolean ) paperJasiReaponse ).booleanValue{);
374
375 // отображение состояния
376 if ( isPaperJaa ) {
377 SwingUtilities.invokaLater ( new TextAppender(
378 printerStatusTextAxea,
379 "Paper jammedAn" ) );
380 }
381 else (
382 SwingUtilities.invokeLater( new TextAppender(
383 printerStatusTextAxea,
384 "No Paper Jan An" ) );
385 )
386
387 // состояние принтера.
388 boolean isPrinting ■
389 ( ( Boolean )printingResponse ).booleanValue();
390
391 // отображение состояния
392 if ( iePrinting ) {
393 SwingUtilities.invokeLater( new TextAppender(
394 printerStatusTaxtAxea,
395 "Printer is currently printingAn" ) );
396 )
397 else (
398 SwingOtilities.invokeLater( new TextAppender(
399 printerStatusTextfccea,
400 "Printer is not printing.\n" ) );
401 )
402
403 // бумах-а m лотке принтера
404 int paperttemaining =
405 ( ( Integer )paperTrayResponse ).intValue();
406
407 // отображение состояния
408 SwingUtilities.lnvokeLatex( new TextAppeAder(
409 pr inter St» tusTeittAxea,
410 "Printer paper tray has " + paperttemaining +
411 " pages ramaining.\n" ) ) ;
412
413 II удаление заданий иэ очереди на печать
414 Object[] pendingPrintJobs =
415 ( Object[] ) pendingPrintJobsRespons*;
416 int pendingPrintJobsNumbex = pendingPrintJobs.length;
417
41B // отображение состояния
419 SwingOtilities.invokeLater( new TextAppendex(
420 printerStatusТекtAxea,
421 "Number of pending print job»: " +
422 pendingPrintJobgNumber + "\n" ) );
423
424
425 } // эавершевие метода checkStatusButtonAction
426 }
Рис. 5,13. Графический пользовательский интерфейс управляющего приложения
В строках 32-56 определен внутренний класс TextAppender для добавления
текста в контейнер Swing в многопоточной среде. В строках 154-158
осуществляется вызов метода startPrinting компонента MBean Printer для того, чтобы
принтер начал процесс печати. Управляющее приложение должно обрабатывать
события, получаемые от компонента МВеап, рассылающего сообщения. В строках
182-183 вызывается конструктор Printer Event Handler (рис. 5.11) для добавления
слушателя подтверждений для компонента МВеап, рассылающего уведомления.
Конструктор принимает два параметра. Первый параметр, RmiConnectorCIient,
является ссылкой на клиент соединителя, через который осуществляется процесс
регистрации. Второй параметр, PrinterEventListener, является ссылкой на класс
PrintcrManagementGUI, который обрабатывает все передаваемые события.
В строках 186-226 осуществляется удаление регистрации компонентов МВеап
Printer и PrinterEventBroadcaster, когда пользователь закрывает окно
приложения. В строке 194 осуществляется удаление регистрации компонентов МВеап
PrinterSimulator, а в строке 198-208 — PrinterEventBroadcaster. Метод ппге-
gisterMBean, осуществляющий удаление регистрации, принимает один параметр,
ObjectName, который задает имя удаляемого компонента МВеап сервера МВеап-
Server.
Метод outOfPaper (строки 281-236) отображает В панели событий событие,
заключающегося в отсутствии бумаги а лотке принтера. Метод IowToner (строки
239-244) отображает в панели событий низкий уровень тонера в картридже. Метод
paperjam (247-252) отображает замятие бумаги. Метод addPaperButtonAction
Java Management Extensions [JMX) 237
(строки 255-278) выполняется, когда пользователь нажимает кнопку Add Paper
(Добавить бумагу в лоток принтера). В строках 258-259 выполняется операция
компонента MBean repIenishPaperTray, которая заключается во вставке в лоток
принтера 50 листов бумаги. Метод cancelPendingPrintJobBButtonAction (строки
281-305) выполняется, когда пользователь нажимает кнопку Cancel Pending
Print Jobs (Удаление заданий на печать из очереди). В строках 285-288
выполняется операция can eel PendingPrint Jobs компонента MBean Printer. Метод check-
Status Button Action (строки 307-425) выполняется, когда пользоаетель щелкает
на кнопке Check Status (Проверить состояние). В строках 318-337 вызываются
методы получения значений свойств isOnline, UPaperJam, UPrinting, getPaper-
Ттау и getPendingPrintJobs компонента MBean Printer. В строках 355-422
осуществляется вывод данных.
5.3.5. Компиляция и выполнение примера
Перед компиляцией исходного кода Java пути к файлам jdmkrt.jar и jdmktk.jar
необходимо включить в CLASSPATH. Сначала откомпилируйте файлы пакета
com.deitel.advjhtpl.jmx, Printer, затем файлы пакета com.deiteI.advjhtpl.jmx.
Printer Management. Наконец откомпилируйте файлы пакета com.deiteI.advjhtpl.
jmx.CIient.
Чтобы выполнить пример, запустите сначала на выполнение Printer
Management Agent. Класс Print erM an agement Agent создает MBean Server и
инициализирует сервис соединителя RMI. Затем запустите приложение ClientPrinter-
Management. Класс CHentPrinterManagement запускает модель принтера и
пользовательский интерфейс для управления принтером. На рис. 5.14 представлено
окно приложения сразу после его запуска.
Рис. 5.14. Начальное состояние окна приложения
На рис. 5.15 представлено окно приложения после исчерпания бумаги в лотке
принтера и нажатия пользователем кнопки Check Status.
На рис. 5.16 представлено окно приложения после нажатия пользователем
кнопок AddPaper и CheckStatus.
238
Глава 5
Рис. 5.15. Состояние принтера после возникновения события отсутствия бумаги в лотке
Рис 5.16. Состояние принтера после вставки в лоток 50 листов бумаги
На рис. 5.17 представлено окно приложения после замятия бумаги и нажатия
пользователем кнопки Check Status.
Наконец, на рис. 6.18 представлено окно приложения после нажатия
пользователем кнопок Cancel Pending Print Jobs и Check Status.
5.4. Ресурсы в Internet и во Всемирной паутине
wwir.aun.coa/softimxc/jevm-dyMaic/MrTica-driven.htal
Здесь представлено новое поколение сетей — сети, управляемые сервисами.
Java Management Extensions (JMX) 239
Рис. 5,17, Состояние принтера после замятия бумаги
Рис. 5.18, Состояние принтера после удаления заданий на печать из очереди
vn.aun.com/softwara/j*v*-dyn«aic/wp_jkxd[40.htnl
Данная страница является официальным изданием, посвященным Java Dynamic
Management Eit.
mn«. sun. coo/aof twaz«/j*va-dyn«aic/wp_jinl_jdmk. html
На этой странице показано, как использовать совместно Jini н Java Dynamic
Management Kit для самоуправления.
www. eun, coo/ software/ Java -dyn*m±e/q».htial
На этом сайте можно кайтн ответы на часто задаваемые вопросы по Java Dynamic
Management Kit.
240 Глава 5
jn il 1 .'Jiiiaiiiiilil.i'Jii 11 im.'Jii 11 Minag—nt_fi html
На этом сайте можно найти статью Java enters management arena with JMX and Java
DMK (Java появляется на арене сетевого управления с J MX u Java DMK),
написанную Максом Гоффом
]ava.sun.com/products/JavaManagan*nt/wp
На этой Web-странице можно найти официальный документ по Java Management
Extensions.
Резюме
• В настоящее время сетевое управление осуществляется в основном с помощью
управляющих приложений посредством агентов.
• Возможность использование существующих агентов ограничена, так как у них
отсутствуют возможности обреботки событий и средства адантапии к меняющимся условиям в сети.
• Возможности статического агента должны быть предопределены на этапе разработки.
• Java Dynamic Management Kit (JDMK) дает разработчикам Java-приложений средства
для создания автоматизированных решений для сетевого управления.
• JMX определяет трехуровневую архитектуру: уровень сетевых ресурсов, уровень агентов
и уровень управлении. Уровень сетевых ресурсов делает любой объект Java управляемым
с помощью его интерфейса управления. Уровень агентов позволяет сдавать управляемые
ресурсы доступными для управления. Уровень управления позволяет управляющим
приложениям получить доступ и взаимодействовать с управляемыми ресурсам через агенты JMX.
■ Реализации JMX предоставляют интерфейсы к существующим протоколам сетевого
управления, так что разработчики могут интегрировать новые управляющие программы
в существующие системы сетевого управления.
• JMX использует компоненты JavaBeans для построения повторно используемого
программного обеспечения.
■ Java Dynamic Management Kit (JDMK) представляет собой одну из многочисленных
реализаций спецификанни JMX.
• Для запуска управляющего приложения, созданного на основе JDMK, необходимо в
переменную окружения CLASSPATH добавить пути к jdmkrtjar и jdmktk.Jar.
• Законченное решение сетевого управления включает в себя управляемые ресурсы, агент
управления и управляющее приложение.
• Основная задача уровня сетевых ресурсов состоит в том, чтобы сделать ресурсы
управляемыми. В качестве ресурсов могут выступать различные устройства, приложения, а также
объекты Java, которыми необходимо управлять с помощью управляющего приложения.
• Компоненты МВеап используются на уровне сетевых ресурсов, чтобы сделать ресурсы
управляемым н.
• Компонент МВеап состоит из двух частей: интерфейса МВеап и класса Java, который
реализует интерфейс МВеап.
• Стандартный компонент МВеап должен следовать паттерну проектирования,
определенному в Java Management Extensions для стандартизации уровня управляемых сетевых
ресурсов.
• Требуется, чтобы интерфейс компонента МВеап имел имя класса, его реализующего, за
которым следует суффикс МВеап.
• Под операцией понимается открытый метод, чье имя не начинается с get, is или set.
• Метод FindMBeanServer класса МВеап Server Fee tory дает возможность получить ссылки
на серверы МВеап для данной виртуальной машины Java.
• Метод invoke интерфейса МВеап Server позволяет вызывать заданный метод для
заданного компонента МВеап.
• Метод createMBean интерфейса МВеап Server создает экземпляр объекта МВеап и дает
ему уникальное имя.
• Агент управления JMX позволяет осуществлять взаимодействие между компонентами
МВеап и управляющим приложением.
• Обычно агент JMX содержит сервер МВеап и набор компонентов МВеап, которые
представляют собой управляемые ресурсы, сервисы управления и хотя бы одпл адаптер
протокола или соединитель, который обеспечивает доступ удаленного управляющего
приложения к агенту.
lava Management Extensions (J MX) 241
• Компоненты MBean представляют управляемые ресурсы, сервисы управления; они
регистрируются сервером МВеап.
• Локальное управляющее приложение взаимодействует с компонентами МВеап напрямую
через сервер МВеап. Удаленное управляющее приложение взаимодействует с
компонентами МВеап косвенно через посредство адаптера протокола или соединитель,
• Метод сreateMBeanServer класса MBean ServerFactory создает cepsep MBean.
• Компонент МВеап широковещательной рассылки уведомлений представляет собой
МВеап. содержащий источник уведомлений.
• Компонент МВеап широковещательной рассылки осуществляет рассылку уведомлений,
полученных от управляемых ресурсов серверу МВеап.
• Классы рассылки событий могут расширять класс NotificationBroadcasterSnpport (пакет
javax.management), чтобы унаследовать сернисы, например, регистрации слушателей.
• В режиме проталкивания, как только cepsep соединителя RM1 получит уведоилеиие, оно
немедленно передается клиенту соединителя RMI.
• Метод nnregisterMBean удвляет ссылку на компонент МВеап на сервере МВеап.
Терминология
add Notification Lis tener, метод
RmiCo и п ее torC I i ent
agent — агент
agent level — уровень агентов
ClientNotificationHandler, интерфейс
connector — соединитель
design pattern — паттерн проектирования
find MBean Server, метод
M Be a n S erver Fac tory
getDefau It Domain, метод MBeanServer
hand leNoti ft cation, метод
NolificationLis tener
instrumentation level — уровень сетевых
ресурсов
intelligent agent — интеллектуальный агент
Java Dynamic Management Kit (JDMK)
Java Management Extensions (JMX)
Java Beans
JDMK
Упражнения для самоконтроля
5.1. Заполните пропуски в следующих предложениях:
a) JMX определяет трехуровневую архитектуру: . ., и .
Ь) делает ресурс доступным для управления.
c) Метод класса MBeanServerFactory осущесталяет получение ссылок на
серверы МВеап, функционирующие на данной виртуельной машние Java.
d) Для создании экземпляра и регистрации компонента МВеап сервером МВеел
необходимо вызвать мэтод интерфейса MBeaaServer.
e) Агент JMX содержит по крайней мер один или , чтобы
удаленные управляющие приложения могли получить доступ к агенту.
f) Компонент МВеап рассылки уведомлений должен реализовать ингерфейс
, чтобы объянить себя источником уведомлений.
5.2. Ответьте, является ли каждое из следующих высказываний истинным или ложным.
Если высказывание ложна, объясните, почему.
a) Свойство в стандартном компоненте МВеап может иметь несколько методов sit.
b) Если при реелиэацни компонента МВеап не определен открытый конструктор, то
компилятор Java выдаст ошибку.
c) Каждый экземпляр объекта МВееп, варегистрнрованный сервером МВеел, должен
иметь уникальное имя.
JMX
JMX management agent — агент управления
JMX
Managed Beans — управляемые компоненты
management level — уровень управления
MBeanException
MBeaHNotificationlnfo
МВеап Server, интерфейс
MBeanServerFactory, класс
notification broadcaster — компонент
рассылки уведомлений
protocol adaptor — адаптер протокола
protocol independent — независимый от
протокола
RefiectionExceptl on
RmiCoanectorClient, класс
scalability — масштабируемость
SNMP
Глава 5
d) С помощью расширения класса NotificatiooBroadcasterSiipport компонент МВеап
рассылки уведомлений наследует сервисы этого класса, так что нет необходимости
реализации интерфейса NotiflcatiooBroadcaster.
e) Интерфейс МВеап должен иметь имя класса реализации МВеап, ва которым следует
суффикс МВеап.
Ответы на упражнения для самоконтроля
5.1. а) уровень сетевых ресурсов, уровень агентов и уровень управления. Ь) уровень
агентов, с) findMBean Server, d) create МВеап. e) адаптер протокола, соединитель, f) Notifl-
eat ionBroadcaster.
5.2. а) Ложно. Только один метод set и один метод get допустимы для свойства в
интерфейсе управления. Ь) Ложно. Компилятор Java по умолчанию создает для компонента
МВеап открытый конструктор без параметров, с) Истинно, d) Истинно, е) Истинно.
Упражнения
5.3. В чем состоит навначение соединителя или адаптера протокола в JMX? В чем
соединитель пли елаптер протокола схож с другими компонентами МВеап? В чем они
различны? Сравните соединитель и компонент МВеап в нашем примере.
5.4. Добавьте адаптер протокола для HTML в МВеап Server, созданный в PrinterManage-
mentAgent.Java. Запустите приложение ClientPrinterHanagement как и ранее.
Укажите URL http;//loealhost:80e2 для просмотра компонентов МВеап,
зарегистрированных сервером МВеап. Сколько вы увидите компонентов МВеел? Объясните почему.
Можно ли различать компоненты, непосредственно зарегистрированные сервером
МВеап от других компонентов? Для выполнения упражнения воспользуйтесь
документацией но JMX API.
5.5. Попробуйте наменять свойства и операции компонента МВеел Printer (рис. 5.5) и
просмотреть результаты с помощью URL, указанного в предыдущем примере. Будут ли
они отличаться от того, что отображается графически пользовательским интерфейсом
приложения CllentPrlnterManagentent.
5.6. Доработайте класс модели принтера Printer Simulator (рис. 5.6), включна в него метод
removePaporJam, который устранял бы любое замятие бумаги. Доработайте
графический интерфейс пользователя (рис. 5.13), включив в него кнопку Remove Paper Jam
(Устранить замятие бумаги). Внесите, если необходимо, изменения в другие файлы
примера, чтобы при возникновении события замятии бумаги пользе бетель мог бы уст-
ренить его, нажав новую кнопку.
5.7. Измените файлы примера, чтобы пользователь мог остановить и включить принтер
удаленно через PrinterManagementGUI.
5.8. Измените компонент МВеап Printer Simulator и графический пользовательский
интерфейс так, чтобы можно было бы удаленно заправлять картридж принтера, когда
уровень тонера в нем достигнет 10%.
Литература
(Frequently Asked Questions.» Java Dynamic Management Kit (2000)
< www.su л. com /seftware/java-dynamic/qa. h tml >.
Goff, Max. «Java in the Management Sphere, Part 2.» (November 1999)
<jw.itworld.eom/j a vaworld/j w -11-1999/jw-ll -management_p. html>.
«Java Management Ex tensions White Paper.» (8 May 2001)
< j a va. su n .com/products/Ja vaManagement/wp/>
*Jini™ Technology and Java Dynamic Management™ Kit Demonstration Spontaneous
Management in the Service Age.» Java Dynamic Management Kit (2000)
< www.Bnn.com/osftware/java-dynam ic/wp_jini_jdml.html >.
*JSR-000003 Java Management Extensions.! (July 2000)
<j cp. org/abouUava /comma n Ityproceaa/final /jsrOOS/jm x_lnatr_agen t. zip>.
«What is the Service-Driven Network?» Dynamic Service Kit Overview (2000)
< www.san. com/Boftware/java-dynamlc /eervice_dri ven. h tm 1 >.
6
Jiro
Цели
• Разобраться с архитектурой
Jiro.
• Научиться определять
местоположение статических
сервисов.
• Разобраться, как работают
сервисы управления, событий,
регистрации, планирования
и транзакций.
• Научиться развертывать
динамические сервисы.
• Научиться создавать
динамические сервисы.
Любовь без дружбы, как дом,
построенный на песке.
Элла Уилл ер Унлкокс
Вы знаете мой метод.
Он основан на учете мелочей.
Артур Конан Дойл
Факты ничего не говорят
сами по себе, пока не связаны
закономерностью.
Луис Агассиз
6.1. Введение
6 2 Уст.нюика"
•*•.
'■J|5P-" if-
G.4. Динамические,и статические сервисы
6.5 Динамические сервисы t
6.5.1. Реализация динамических ерр&исок
G 6. Статические сервисы ';. '
6.0 1. Определение месюположения стлтичсских-сервисов
с помощью класга SfrvircFinder
6.6.2. СёдоИефбмтии
6.0.3. Сервис регигтрл^ии . ■ ;^
"" ^планировании t -Г ■*■ *
^Р1наЮ)Ча*ских сернисов
1ШнамичЕ>< ких сервисов ,
КУЧ'1 , - '
1Ййе<политик управления , ,_
*----■■"-ния по пЬ^ноодсистемы-упривления
10,'Всемирной паутине а >
*№• '&!&*•,»* /' *■
6.1. Введение
V,ipaB,i i i ■ i vi ■ ■ ■ ■ , ii м
ЛОВИТСЯ В.. . I.'.IUIV ,..■',,,■. !■ ■■ ■ I-
сетей. В сетях могут функционировать устройства и компьнугеры различных
производителей, архитектур, под управлением различных операционных систем. Все
это увеличивает сложность и стоимость эксплуатации сетей.
Для снижения сложности и стоимости системы управления должны быть:
1. автоматизированными, чтобы свести к минимуму вмешательство
персонала;
2. централизованными, чтобы распределенными ресурсани можно было бы
управлять из одного места;
3. стандартизированными, чтобы обеспечить взаимодействие компонентов
программного обеспечения систем управления;
А. открытыми, чтобы программное обеспечение систем управления могло
взаимодействовать с различными типами управляемых ресурсов в сети;
5. не зависящими от платформы, чтобы можно было бы работать с данными
различных платформ;
6. простыми в развертывании и использовании;
7. доступными.
Основанная еа Java технология Jiro™ предоставляет возможности для
разработки систем управления распределенными ресурсами в гетерогенных сетях. Jiro
является реализацией спецификации Federated Management Architecture (FMA —
Интегрированной архитектуры управления), разработанной Java Community
Process и предназначенной для взаимодействия между гетерогенными
управляемыми ресурсами, включая системы, устройства, приложения. Технология Jiro
поддерживает трехуровневую архитектуру систем управления (рис. 6.1). Верхний
уровень называется клиентским уровнем. Клиент на верхнем уровне определяет
местоположение и осуществляет взаимодействие с сервисами управления. Средний
уровень технологии Jiro предоставляет как статические, так и динамические
сервисы. Нижний уровень состоит из гетерогенных управляемых ресурсов.
Клип
Статические и динамические
сервисы управления Jiro
Рис. 6.1. Трехуровневая архитектура управления, поддерживаемая технологией Jiro
Кроме Jiro существует еще ряд индустриальных стандартов. Среди них
наиболее известны SNMP и CIM. Simple Network Management Protocol (SNMP —
простой протокол сетевого управления) является стандартным протоколом
управления сетевыми устройствами в сетях TCP/IP. Common Information Model (CIM —
общая информационная модель) определяет стандартную модель для описания
управляющей информации в сетях. Jiro поддерживает оба стандарта.
Jiro не управляет ресурсами непосредственно. Вместо этого Jiro предоставляет
инфраструктуру управления и сервисы, необходимые для разработки
управляющих приложений. Инфраструктура управления использует систему активации
RMI, Jini и сервер классов. Система активации RMI запускает или перезапускает
по мере надобности сервисы. Jini предоставляет динамическнй сервис поиска,
которая позволяет находить сервисы без предварительной информации об их
местонахождении. Сервер классов обеспечивает динамическую загрузку классов по сети
клиентами.
6.2. Установка
Для разработки управляющего приложения с помощью Jiro необходимо
сначала убедиться, что установлен Java SDK версии не ранее 1.3.x. После этого
необходимо загрузить и установить Jiro Runtime Enoironment к Jiro Technology Software
Development Kit (Jiro SDK). Jiro Runtime Environment и Jiro SDK для Windows
NT/2000 и Solaris могут быть загружены по адресу:
www.j iro.com/downloads
Здесь мы обсуждаем версию для Windows NT/2000. Действия по установке Jiro на
платформе Solaris аналогичны. Хотя Jiro можно загрузить свободно, нужно
сначала зарегистрироваться и принять лицензионное соглашение. На момент
публикации книги текущей версией Jiro Runtime и Jiro SDK была 1.5.
При установке Jiro Runtime нужно будет ответить на вопрос о местоположении
Java v. 1.3. Нужно будет также ответить на вопрос об имени домена управления
Jiro. Домен управления Jiro содержит управляемые ресуреы и сервисы для
управления этими ресурсами. Имя домена управления Jiro должно быть строкой, не
содержащей пробелов. Формат имени домена имеет вид: jiroidomainName. Для
уникальности domainName используйте в качестна имени имя хоста, на котором
установлен Jiro.
При установке Jiro SDK необходимо указать местоположение Java v. 1.3.x
и имя домена управления Jiro. Нужно указать то же самое domainName, что и при
установке Jiro Runtime Environment.
После установки Jiro Runtime Environment и Jiro SDK необходимо внести
изменения в переменную окружения PATH, включив пути к исполняемым модулям
Jiro. Исполняемые модули Jiro расположены в каталоге JiroSDK\bin.
Ш Общая методическая рекомендация 6.1
Имя домена управления Jiro должно быть уникальным внутри области
IP-мульткастинга1. При выполнении примеров используйте имя localhost
для домена управления, чтобы обеспечить его уникальность.
6.3. Запускаем Jiro
Перед запуском Jiro необходимо указать имя домена управления. Если имя
домена управления не указано при установке Jiro, то его необходимо задать в файле
JiroS DK\e tc\ server.con fig. В комплект поставки Jiro входит утилита с
графическим пользовательским интерфейсом, позволяющая настроить, запустить и
остановить Jiro. Запустить эту утилиту можно с помощью пакетного файла igniter.bat,
расположенного в подкаталоге bin каталога, в который произведена установка
Jiro. На рис. 6.2 показано окно утилиты после ве запуска.
ШшшйшШШ1Ш1!§&щШ>{
пользовательский интерфейс утилиты Igniter непосредствен н<
Рис. 6.2. Гра<
после се запуска
Пять красных индикаторов слева означают, что Jiro не запущен. Запустить Jiro
можно с предварительной очисткой, установив флажок Clean before start, запуск
о также осуществить и без очистки. Процесс очистки удаляет ранее разверну-
Область IF-мульткастинга определяет область, где функционируют протоколы г
сервисов Jiro.
тые динамические сервисы и восстанавливает информацию, измененную при
предшествующих запусках Jiro. Нажатие кнопки Start запускает Jiro с настройками
по умолчанию даже в том случае, если файлы настройки были изменены. При
запуске Jiro одновременно запускаются сервер классов, nnid, сервис транзакций,
совместно используемая станция Jiro, сервис управления, сервис регистрации,
сервис событий и сервис планирования. Перечисленные сервисы обсуждаются в
разделе 6.6. Сервис транзакций, совместно используемая станция Jiro, сервисы
управления, регистрации, событий и планирования регистрируются сервисом
поиска Jim, который также запускается при нажатии кнопки Start. При успешном
завершении процесса запуска все пять индикаторов становятся зелеными. Для
слежения за процессом запуска установите в меню Options флажок Display
Console. На рис. 6.3 показана консоль утилиты Igniter с сообщениями о процессе
запуска. Сообщения об ошибках в процессе запуска можно увидеть, щелкнув
мышью на закладке Errors.
Для настройки Jiro необходимо нажать кнопку Stop, если Jiro уже запущено,
затем перейдите к меню File и выберите Edit Configuration, Информацию о
параметрах настройки можно получить в документе Jiro Technology Installation and
Configuration Guide (Руководство по установке и настройке технологии Jiro).
Этот документ находится в подкаталоге docs н доступен как в формате PDF
(instaII_config.pdf) и PostScript (install_config.pe).
Ш Общая методическая рекомендация 6.2
Нельзя вносить изменения в файлы настройки при запущенном Jiro.
)n«1.3O_02»frnmBi
Cleaning service *evBnt>
DeWttng с ■tflesUiroSDKtelcl tscr»tctilevenl3vc_log1eveiitevc.sef
ЭеМШд C-WesUlto6DKietrt tscrrtthlewntovtjog
Ctetning эеШсе <rmid>
DelelnflCatleSUlraSOMelrtUtrrtMfmldJoglLogfile 1
DeleSnaCDileeUirtiSOKWteHiCratttftTTim^Weffinapeholl
MMngiCfflleauiroSOKlelcl Wcr»KtftmM JogWersioti.Number
3ll(tlng:CWnUlroSDi<№tcl fcciafchVmldJog
Рис. 6.З. Сообщения о процессе запуска Jiro после завершения процесса запуска
6.4. Динамические и статические сервисы
Сердцевиной систем управления на основе Jiro является программное
обеспечение управления. В свою очередь программное обеспечение управления состоит из
одного или более сервисов. Эти динамические сервисы и представляют
программное обеспечение, управляющее сетью.
248
Глава б
Разработчики, создающие системы управления сетями, знают, что в системе
должен быть набор стандартных сервисов, которые должны решать
фиксированный набор задач. Чтобы помочь разработчикам и сократить время разработки,
в Jiro включен набор стандартных сервисов, требуемых большинством систем
управления. Во время инициализации Jiro запускает последовательность
статических сервисов, называемых также основными сервисами, и регистрирует их с
помощью сервиса поиска Jini. Различие между динамическими и статическими
сервисами состоит в том, что динамические сервисы определяют функциональность
системы управления, а статические сервисы представляют собой инструменты,
которые доступны всем динамическим сервисам и упрощают разработку. В число
статических сервисов входят:
1. сервис событий Jiro, осуществляющий рассылку уведомлений о событиях
зарегистрированным слушателям;
2. сервис планирования Jiro, который возбуждает события в заданное время;
3. сервис регистрации Jiro, регистрирующий заданные события и ошибки;
4. сервис транзакций Jiro, обеспечивающий синхронизированный доступ
к методам;
5. сервис управления Jiro, создающий контроллеры для обеспечения
синхронизированного доступа к динамическим сервисам в мкогопоточной среде.
В каждом домене Jiro имеется по одному экземпляру каждого стандартного
сервиса для использования удаленными клиентами или динамическими
сервисами. [Примечание. Сервисы транзакций и управления не ре осматриваются в этой
главе.] За информацией обращайтесь на сайт www.jIro.com.]
6.5. Динамические сервисы
Jiro обеспечивает каждый домен управления станцией, на которой
установлены динамические сервисы. Это дает возможность удаленным клиентам вызывать
динамические сервисы. Станция запускается при запуске Jiro.
В Jiro динамический сервис, дающий доступ к управляемому ресурсу или
динамическим сервисам, называется фасадом управления. Фасад компонентов Jiro
дает возможность другим компонентам управлять устройствами, доступ к
которым дает фасад управления. Фасад обеспечивает единственную точку
взаимодействия между компонентами Jiro и динамическими сервисами фасада. Фасад
управления изолирует клиентов от сложных низкоуровневых структур и поведений.
Фасад управления является примером паттерна проектирования Facade. Этот
паттерн проектирования снижает сложность системы, тан как клиент
взаимодействует только с одним объектом, называемым объектом фасада. При разработке
программного обеспечения паттерн проектирования Facade защищает
разработчиков от сложности подсистем. Разработчик должен знать только об операциях
фасада, а не обо всех операциях подсистемы. При управлении автомобилем вы знаете,
что нажатие педали газа увеличивает скорость автомобиля, но вы можете не знать
точно, как нажатие педали газа вызывает увеличение скорости автомобиля.
Объект фасада обеспечивает простой интерфейс с поведениями подсистемы. В примере
с автомобилем педаль газа представляет собой объект фасада к двигателю
автомобиля. Клиентский объект использует объект фасада для взаимодействия с
объектами, скрытыми за фасадом. Клиенту остается неизвестно, как функционируют
объекты, скрытые за фасадом, так что сложность подсистемы остается скрытой от
клиента. В Jiro фасад управления представляет собой объект фасада, а
компоненты Jiro, использующие фасад управления для управления ресурсам, являются
объектами-клиентами.
Jiro
249
Чтобы сделать динамические сервисы доступными в домене управления,
провайдеры должны следовать двум правилам: реализовывать динамический сервис
(это должно быть сделано разработчиком) и развернуть динамический сервис на
станции в домене управления (это должно быть сделано системным
администратором). В оставшейся части раздела мы покажем, как разрабатывается и
развертывается динамический сервис, а также как получить доступ к динамическому сервису
для управления принтером. Как и в пятой главе, в данной главе в качестве
управляемого ресурса используется модель принтера.
Интерфейс принтера обладает ограниченным набором операций. Наш
динамический сервис отображает эти операции. Мы создадим консоль управления,
которая позволяет администратору удаленно управлять принтером. Это приложение
является простыми примером системы управления, использующей Jiro.
6.5.1. Реализация динамических сервисов
Для реализации динамического сервиса разработчик должен прежде всего
определить открытый интерфейс сервиса. Открытый интерфейс дает доступ
клиентам и другим динамическим сервисам к операциям, осуществляемым с
управляемым ресурсом1.
Printer Management (рис. 6.4) предоставляет доступ к операциям управления
принтером. С помощью данного интерфейса разработчик может получить доступ
к текущему состоянию принтера посредством методов isPrinting, isOnline, isPa-
perJam и get Pen dlngPrin ting Jobs. Управление принтером может осуществляться
разработчиком с помощью операций addPaper, addToner и cancclPendingPrint-
Jobs. В нашей реализации динамический сервис PrinterManagement планирует
выключение принтера в конце каждой недели. Операция terminate Scheduled-
Tasks позволяет клиентам отменить запланированные динамическим сервисом
PrinterManageшелt задания.
1 // PrinterManagementoava
2 // Здесь определен интерфейс к динамическому сервису.
3 package com.deitel .adv^htpl. jiro.DynamieServiee. service,-
4
5 // Базовые пакеты Java
6 import java.nni.*;
7 import java.util.*;
8
9 // Базовые пакет» Jini
10 import net.jini.core.event.*;
11
12 public interface PrinterManagement
13 extends RemoteEventListener (
14
15 public void addPaper( int amount )
16 throws RemoteBxception;
П
18 public boolean isPrinting () throws BemoteException;
Id
20 public boolean isPaperJamO throws RemoteException,-
21
1 Ресурсы Jiro не ограничены физическими устройствами в сети. В их число также
включаются динамические сервисы, доступ к которым осуществляется через станцию Jiro
или с аомощью других средств.
22 public int getFaperXnTrayO throws RemoteBxception;
23
24 public boolean isOnlinef) throws RemoteBxception;
25
26 public void cancelPendingPrintJobs() throws RamoteException;
27
28 public void terminateScheduledTaslcs () throws RamoteException;
29
30 public void addToner() throws RemoteBxception;
31
32 public String!] getPendingPrintJobs() throws RemoteBxception;
33 >
Рис. 6.4. Определение интерфейса Printer Management
Класс PrinterManagementlmpi (рис. 6.5) представляет собой реализацию
динамического сервиса, который мы разрабатываем для модели принтера (класс
Printer на рис. 6.8). Заметим, что PrinterManagementlmpi использует статический
сервис, о чем мы поговорим в соответствующем разделе (см. раздел 6.6).
1 // PrinterManagementlmpi.Java
2 // Этот класс планирует периодически включение/выключение
3 // принтера и видает сообщения для сервиса регистрации
d // при жоаккхновешхи ошибок.
5 package com.deitel.advjhtpl.jiro.DynamicServica.service;
б
7 // Баеоные пакеты Java
8 import java.io.Serializable;
9 import java.rmi.*;
10 import java.util.*;
11
12 // Стандартны* расширения Java
13 import javen.swing.*;
14
15 // Бааовне пекета Jini
16 import net.jini.core.event.'
17 import net.jini.core.entry.'
18 import net.jini.core.lease.1
19
20 // Пакета распираний Jini
21 import net.jini.lease.LeaseRenewalManeger;
22 import net.jini.lookup.entry.*;
23
24 // Пакеты Jiro
25 import javax.fma.services.ServiceFindar;
26 iaport javax.fma.services.event.EventSarvice;
27 import javax.fma.services.log.LogMesaage;
28 import javax.fma.services.log.LogService;
29 import javax.fma.services.scheduling.SchedulingService;
30 import javax.fma.services.scheduling.SchedulingService.'
31 import javax.fna.utll.*;
32
33 // Пакеты Deitel
34 import com.deltel.advjhtpl.jiro.DynamicService.printer.'
35
36 public class PrinterManagementlmpi
37 implements PrinterManagement (
38
39 private Printer printer;
40 private LogServica logService = null;
41 private Lease observerLease;
42 private LeaseRenewalManager leaseRenewalHanager,-
43 private Ticket turnOffPrinter;
44 private Ticket turnOnPrinter;
45
46 // конструктор по умолчанию
47 public PrinterManagementXmpl()
48 (
49 System.out.println( "Dynamic service started.\n" );
50
51 // запуск принтера
52 printer = new Printer () ;
53 Thread printerThread = new Thread( printer ) ;
54 printerThread.start();
55
56 // подписка на события принтера
57 try (
58
59 // получение сервиса событий
£0 EventService eventService =
61 ServiceFinder.getEventService();
62
63 // получение сервиса регистрации
64 logService = ServiceFinder.getLogService();
65
66 // получение слушателя
67 PrinterEventListener listener =
68 new PrinterEventListener( this >;
69
70 // подписка на слушатель для события
71 // ".Printer.Error"
72 observerLease = eventService.sub scribeObserver(
73 ".Printer.Error", listener, null, 10 * 60 * 1000 );
74
75 // обновленке аренды слушатели
76 leaseRenewalManager = new LeaseRenewalManager();
77 leaseRenewalManager.renewOntil(
78 observerLease, Lease.FOREVER, null };
79
80 // получение сервиса планирования
81 SchedulingService schedulingService =
82 ServiceFinder.getSchedulingService();
83
84 // планирование выключения принтера
85 // в 20.00 каждую пятницу
86 GregorianCalendar calender ■ new GregorianCalendar();
87 . calendar.set( 2001, 7, 27 };
88 Date startOate = calendar.getTimef);
89 calendar.set( 2003, 7, 27 ) ;
90 Date endDate = calendar.getTimef);
91 intl) monthsOff = ( Calendar.JANUARY,
92 Calendar.FEBRUARY, Calendar.MARCH, Calendar.APRIL,
252
Глава 6
93 Calendar.MAY, Calendar.JUNE, Calendar.JULY,
94 Calendar.AUGUST, Calendar.SEPTEMBER,
95 Calendar.OCTOBER, Calendar.NOVEMBER,
96 Calendar.DECEMBER };
97 int[] daysOfWeekOff = ( Calendar.FRIDAY );
98 int[) noursOff = ( 20 );
99 int[j minutesOff = ( 0 );
100
101 // планирование отключения принтера
102 Schedule turnOffSchedule =
103 schedulingService.newRepeatedDafceSchedule(
104 startDate, endDate, monthsOff, null,
105 daysOfWeekOff, hoursOff, minutesOff,
106 calendar.getTineZone() );
107
108 // создание описания сообщения
109 LocalixableHessage turnOffMeseage ~
110 new LocalizableHessage( PrinterHanagementlmpl.class,
111 "TurnOffPrinter", null, null );
112
113 // создание объекта для задачи
114 // выключения принтера
115 MarshalledObjact handbackOff = new Marshalledobjееt(
116 new String( "turn-off" ) );
117
118 // планирование задачи и получения бинета
119 // для планируемой задачи
120 turnOffPrinter = schedulingService.scheduleTask(
121 listener, turnOffHessage, turnOffSchedule,
122 SchedulingService.NONE, handbackOff );
123
124 // определение задания для включения принтера
125 // в 7.00 каждый понедельник
126 calendar = new GregoriалCalendar();
127 calendar.set( 2001, 7, 27 );
128 startDate = calendar .gatTijne () ;
129 calendar.set( 2003, 7, 27 );
130 endDate = calendar.getTime();
131 int[] monthsOn = ( Calendar.JANUARY,
132 Calendar.FEBRUARY, Calendar.MARCH, Calendar.APRIL,
133 Calendar.MAY, Calendar.JUNE, Calendar.JULY,
134 Calendar.AUGUST, Calendar.SEPTEMBER,
135 Calendar.OCTOBER, Calendar.NOVEMBER,
136 Calendar.DECEMBER };
137 int[] daysOfWeekOn = ( Calendar.MONDAY };
138 int[] hoursOn = { 7 );
139 int[j minutesOn « ( 0 );
140
141 // плакирование включения принтера
142 Schedule turnOnSchedule =
143 schedulingService.newRepeatadDateSchedule(
144 startDate, endDate, monthsOn, null,
145 daysOfWeekOn, hoursOn, minutesOn,
146 calendar.getTineZone() );
147
14Я // создание описания сообщения
149 LocalizableMessage turnOnMessage =
150 new LocalizableMessage( PrinterManagementlmpl.class
151 "TurnOnPrinter", null, null );
152
153 // создание объекта
154 // для выключения принтера
155 MershalledObject handbackOn = new MarshalledOb;ject(
156 new String( ■'turn-on" ) ) ;
157
158 // планирование задачи и получение билета для
159 // планируемой задачи
160 turnOnPrinter = schedulingService.scheduleTask(
161 listener, turnOnMessage, turnOnScbedule,
162 SchedulingService.NONE, handbackOn );
163
164 ) // завершение try
165
166 // обработка исключений при планирования задачи
167 catch ( Exception exception ) (
168 System.out.println( "FrinterMenagementlmpl: " +
169 "Exception occurred when scheduling tasks." ):
170 System.out.println( "Please read debug file . ..\n,r);
171 Debug.debugException( "schedulling task", exception );
172 )
173
174 } // завершение хонструктора PrinterManagementlmpl
175
176 // силтие запланированных задач
177 public void terminateScheduledTasks()
178 {
179 // снятие задач включения/выключения принтера
180 try (
181 turnOffPrinter.cancel (>;
182 turnOnPrinter.cancel () ;
183 }
184
185 // обработка исключений при снятии задач
186 catch ( Exception exception ) [
187 System.out.printlnf "PrinterManageaentlmpl: " +
188 "Exception occurred when canceling tasks." );
189 System.out.println( "Please read debug file ...\n" );
190 Debug.debugException(
191 "cancel scheduled task" , exception ) ,-
192 }
193
194 ) // завершение снятия запланированных задач
195
196 // добавление бумаги
197 public void addPaper( int amount )
198 {
199 System.out.println(
200 "PrinterManagementlmpl: Adding paper ...\n" );
201 printer.replenishPaperTray( amount );
202
203
204
205
206
207
208
209
210
211
212
21Э
214
215
216
217
218
219
220
221
222
223
224
225
226
227
22В
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
public boolean isPrintingt)
return printer.isPrinting();
// хамят» ли бумага?
public boolean isPaperJam()
return printer.isPaperjam();
// получение числа листе* » лотке принтера
public int getPaperlnTray()
return printer.getPaperlnTray(>;
// получение мденкй а очереди
public String[1 getPendingPrintJobs(}
return printer.getPendingPrintJoba(};
// включен ля принтер?
public boolean isOnlinef)
return printer.isOnlineO ;
public void cancelPendingPrintJobs()
System.out.println( "PrinfcerManageinentlmpl:
+ "Canceling pending print jobs . . . \n" '.
printer.cancelPendingPrintJobs();
// получение уведомлении
public void notify( Remot«Event remoteEvent }
throws UnknownEvantException, RemoteExcaption
String substring =
"com.deitel.advjhtpl.jiro.DynemicService.printer"
String source = ( String ) renoteBvant.getSourc*();
' source.substring( 0, substring.length(} } ,-
// события принтера
if ( source.equals( substring ) >
eventBandler( renoteEvent );
else // Запланированная задача
performTask( renoteEvent );
// добавление тонера
public void addTonerf)
261 (
262 System.out.printlnf
263 "PrinterManagementlmpl: Adding toner ...\n" );
264 printer.addToner();
265 }
266
267 // выполнение задачи, когда наступает запланированное время
268 private void performTask( RemoteEvent remoteEvent )
269 (
270
271
272
Z73 // получение типа задачи
274 String type =
275 ( String > remoteEvent.getRagistrationObject().get();
276
277 // аиклшенив принтера
278 if ( type.equalsf "turn-off" ) )
279 printer.setOfflinef);
280
281 // включение принтера
282 else if ( type.equals( "turn-on" } }
283 printer.aetOnline(};
284 )
285
286 // обработка исключений при выполнения эапланированиой задачи
287 catch (Exception exception) (
288 System.out.println( "PrintemanagementImp1: " +
269 "Exception occurred when performing tasks." );
290 System.out.println( "Please read debug file . . ,\n" ) •'
291 Debug.debugException(
292 "perform scheduled task", exception );
293 )
294
295 } // эашериение метода performTask
296
297 // обработка события
298 private synchronized void eventHand1er (
299 RemoteEvent remoteEvent )
300 (
301 String source = ( String ) remoteEvent.getSourcef);
302
303 // генерация сообщении для журнала
304 Serializable params[) = new Serializable[ 2 1;
305 parems[ 0 ] = source;
306 params[ 1 ] = new Date();
307
308 // определение локализируемого сообщения
309 LocalizableHessage localizableMessage =
310 new LocalizableMessage( Print«ManagementImp1.class,
311 "Event", params. Locale.US );
312
313 // определение сообщения
314 LogMessage logMeasage = new LogMessage(
315 localizableMessage, LogMessage.TRACE +■ ".printer."
316 + source, null );
317
318 // отправка сообщении
319 try (
320 logService.log( logMessage };
321 }
322
323 // обработка исключений при отправке сообщений
324 catch ( Exception exception } (
325 System.out.println( "PrinterManageaentl^pl: " +
326 "Exception occurred when posting log message." );
327 System.out.println( "Please read debug file ...\n" );
328 Debug.dabugExcaption{ "log service", exception );
329 )
330
331 } // завершение eventHandler
332
333 // объект Entry
334 private Entryll getLookupEntries(1
335 [
336 return ( new EntryП {
337 new Servicelnfof "PrinterManagementlmpI",
33B "Deitel Association, Inc.",
339 "Deitel Association, Inc",
340 "1.0", "Model 0", "0.0.0.1" )
341 }
342 } ;
343 }
344 }
Рис. 6.4 PnnterManagementlmpl - реализация интерфейса PrinterManagement
Доступ к динамическим сервисам осуществляется посредством
объектов-посредников, экземпляры которых создаются клиентами. Утилита jirocw создает
класс-посредник для динамического сервиса во время развертывания. Мы обсудим
развертывание управляющего приложения в разделе 6.7. Мы сгенерируем Printer-
Ma па gem entlmplProxy — класс-посредник для реализации динамического
сервиса PrinterManagementlmpI с помощью утилиты jirocw. Создать динамический
сервер можно, только создав экземпляр объекта-посредника. Когда клиент создает
экземпляр PrinterManagementlmpI Proxy, посредник удаленно вызывает
конструктор без параметров для динамического сервиса PrinterManagementlmpI.
Если динамический сервис реализует метод getLookupEntrUa (строки 334-343),
станция Jiro предоставляет ссылку PrinterManagementlmpI Proxy на сервис
поиска Jim домена с атрибутами объекта Entry, возвращенными getLookupEntries ва
этапе инициализации. Динамические сервисы я клиенты, которым необходимо
взаимодействовать с динамическим сервисом после того, как клиенты создадут
объекты-посредники, должны это делать с помощью ссылки Printer Manage men t-
IraplProxy, хранимой сервисом поиска Jini.
В строках 52-54 запускается программный поток, который моделирует работу
управляемого принтера. Метод ran класса Printer управляет работой iiotoi
В строках 60-82 осуществляется получение ссылок на несколько статических сер
висов, используемых динамическим сервисом Printer Management. Все
упомянутые здесь статические сервисы подробно обсуждаются в разделе 6.6. Эти
стандартные статические сервисы могут быть получены с помощью вспомогательного кл;
са ServiceFinder (обсуждается в разделе 6.6.1). В строках 60-61 осуществляется
получение ссылки на сервис событий (раздел 6.6.2). В строке 64 осуществляется
Jiro
257
получение ссылки на сервис регистрации (раздел 6.6.3). В строках 67-68
создается новый PrinterE vent Listener для прослушивания событий, порождаемых
управляемым принтером. В строках 72-73 осуществляется подписка слушателя Printer-
EventListener на сервис событий, В строках 80—81 обновляется аренда на
слушатель. В строках 81-82 осуществляется получение ссылки на сервис планирования
(раздел 6.6.4). В строках 86-122 и 126-162 планируются два события:
выключение принтера в пятницу вечером и включение его в понедельник утром.
Метод terminatescbeduledTaвks (строки 177-L94) завершает все
запланированные задачи. Метод add Paper добавляет бумагу в лоток принтера с помощью
делегирования вызова объекту Printer. Методы UOnline, UPrintmg, isPaperJam, get-
PaperTray, get PendiagPrint Jobs, addToner и Cancel PendiagPrint Jobs устроены
диалогично методу addPaper, делегируя соответствующие вызовы объекту Printer.
Метод notify (строки 248—357) получает делегированные уведомления от
PrinterEventListener и определяет, возбуждены ли события объектом Printer или сервисом
планирования. Метод notify вызывает метод performTask (строки 268-295), если
событие возбуждено сервисом планировании. В строках 252—256 определяется тип
события и выполняется соответствующее действие. Если событие возбуждено
объектом Printer, то метод notify вызывает метод eventHandler (строки 298-331). Метод
eventHandler записывает сообщение, относящееся полученному событию
(строки 304-320).
В строках 334-343 реализуется метод getLookupEntries, это является
единственным способом объявить класс динамическим сервисом. Класс, имеющий метод
getLookupEntries, называется точкой входа. Динамический сервис может быть
реализован более чем одним классом. В спецификации Jiro указано, что только
один объект может быть точкой входа в экземпляр динамического сервиса.
Создание экземпляра объекта, являющегося точкой входа, является единственным
способом создания динамического сервиса, а также классов, от которых он зависит.
Метод getLookupEntries возвращает множество входов. Эти входы используются
станцией для регистрации посредников динамических сервисов в сервисе поиска
Jini домена.
На рис. 6.6 (Printer Event Listener. Java) приводится слушатель событий,
создаваемый в строках 67-68 листинга на рис. 6.5. PrinterEventListener реализует
интерфейс RemoteEventListener — Jiro требует этого для подписки на события. На
рис. 6,6 приводится наша реализация RemoteEventListener. Сервис событий
вызывает метод notify, когда случается событие.
1 // PrinterEventListener.java
2 // Этот класс определяет слушатель всех событий
3 // принтера.
4 package com.deitel.advjhtpl.jiro.DynamieService.service;
5
6 // Основные пакет» Java
7 import java.nni.*;
8 import Java.mi.server.UnicastRemoteObject;
9
10 // Базовые пакеты Jini
11 import net. jini.oore.event. *,-
12
13 public class PrinterEventListener
14 implements RemoteEventListener {
15
16 private RemoteEventListener eventListener;
17
18 // PrinterEventListener constructor
9 3si 2M
2S8
Глава 6
19 public PrinterEventListener( RemotaEvantltisfcener listener )
20 {
21 eventLiatener =• listener;
22
23 // экспорт объекта-эаглушхи
2* try {
25 UnicaatRamoteObject.exportObject( this ) ;
26 )
27
28 // обработка исключений при экспорте еалпуажи
29 catch ( RemoteExcoption remoteException } (
30 remoteException.printstackTraceО;
31 }
32
33 > // аажерлевие конструктора PrinterEventListener
34
35 // получение подтверждения
36 public void notify( RemoteEvent remoteEvent }
37 throws nnxnownEventException, RemoteException
3B {
39 // передача уведомления
40 eventLiatener. notify ( remoteEvent ) ,-
41 )
J2J
Рис. 6.6. PrinterEventListener - слушатель, используемый всеми классами, подписанными
на события
6.6. Статические сервисы
Printer (рис. 6.8) и PrinterManagementlmpl (рис. 6.5) используют статические
сервисы Jiro. Printer публикует события в сервисе событий (строка 320 на рис. 6.8).
Класс PrinterManagementlmpl использует сервис событий для прослушивания
событий, сервис регистрации и сервис планирования, плакирующий отключение
принтера по пятницам в 20:00 и включение по понедельникам в 7:00. В последующих
разделах говорится о том, как получить и использовать статические сервисы Jiro.
Обсуждаются только те статические сервисы, которые используются в примере.
6.6.1. Определение местоположения статических сервисов
с помощью класса ServiceFinder
Перед использованием статических сервисов необходимо получить посредники
сервисов. Класс ServiceFinder (пакет javax.fm a, service) содержит методы для
обнаружении статических сервисов. Этот класс содержит десять методов — по два на
каждый сервис. Для получения посредника для статического сервиса приложение
или динамического сервиса либо вызывает метод Service Finder. getServiceName
без параметров, который определяет местоположение сервиса для локального
домена управления1, либо вызывает метод Service Finder .getServiceNa me с двумя
параметрами, который определяет местоположение сервиса для заданного домева.
Например, метод get Log Service без параметров возвращает сервис регистрации
Клиенты могут найти информацию для локального домена управления через системное
свойство javax.fma.domain.
Jiro
259
для локального домена управления. Аналогично, статические сервисы могут быть
также найдены через сервис обнаружения Jini. ServiceFinder инкапсулирует
вызовы сервиса обнаружения Jini.
6.6.2. Сервис событий
Объекты событий инкапсулируют то, что случается в сети. Это позволяет
обрабатывать события, возникающие в одной части сети, в другой части сети. Система
публикации-подписки на события, к которым относится Jiro, являются одним из
видов систем, управляемых по событиям. Такие системы обычно включают в себя
три типа элементов; публикатор событий, распространяющий события но сети,
подписчик событий, прослушивающий события и расположенный, возможно,
в другом месте сети. Сервис событий яэляется промежуточным звеном между
публикатором событий и подписчиком. В Jiro публикатор событий передает события
или серии событий сервису событий. Подписчик получает события от сервиса
событий. Подписчик может подписаться только на некоторые виды событий,
известные также как темы.
Подписчик на события может подписаться у сервиса событий как
слушатель-наблюдатель или как ответственный слушатель. Слушатель-наблюдатель
может получать события, но не может их обрабатывать или уничтожать.
Ответственный слушатель имеет приоритет реагировать или уничтожать дакные события.
Ответственный слушатель должен принять решение, уничтожать или не
уничтожать событие. Бели принято решение не уничтожать событие, то его метод notify
должен возбудить исключение Even t Not Нал dleExcept ion. Это ведет к тому, что
сервис событий передаст событие другому ответственному слушателю в цепи.
Сервис событий Jiro использует паттерн проектирования
Chain-of-Responsibility, чтобы стандартизировать обработку событии ответственными слушателями.
Часто система во время своей работы должна определить объект, который будет
обрабатывать данное сообщение. Например, рассмотрим офис с тремя телефонными
линиями. Когда кто-либо звонит в офис, вызов поступает на первую линию, если
же первая линия занята, то вызов поступает на вторую линию, если занята вторая
линия, то задействуется третья линия. Если же все три линии заняты, то
автоответчик просит позвонившего подождать освобождения одной из линий. Когда
случается событие, то все слушатели-наблюдатели и первый в цепи ответственный
слушатель получают событие.
Паттерн проектирования Chain-of-Responsibility позволяет системе во время
выполнения определить объект, который будет обрабатывать сообщение. Этот
паттерн позволяет рассылать сообщение объектам в цепи объектов. Каждый объект
в цепи может либо обработать сообщение, либо передать его следующему объекту
в цепи. Например, первая линия в примере представляет собой первый объект
в цепи ответственности, вторая линия — второй объект, третья линия — третий
объект, а автоответчик представляет собой четвертый объект в цепи. Заметим, что
это не последний объект в цепи — следующим объектом является первая
доступная линия. Цепь создается динамически в ответ на отсутствие или присутствие
определенного обработчика сообщений. В Jiro публикатор событий посылает событие
ответственному слушателю, если он не может обреботать событие, то сервис
событий посылает событие следующему ответственному слушателю. Сервис событий
распространяет событие слушателям, пока один из них не обработает событие.
Класс PrinterManagementlmpl (рис. 6.5) является примером класса,
подписывающего слушатель на сервис событий. Класс PrinterManagementlmpl
регистрирует PrinterEventListener в качестве слушателя-наблюдателя во время
инициализации. В строках 72-73 осуществляется подписка на событие с ".Printer.Error"
в качестве темы. Метод snbscribeObserver принимает четыре параметра: строку
260
Глава б
с темой события и слушатель, реализующий RemoteEventListener, так что могут
быть высланы уведомления, когда сервис событий • получает события по данной
теме; объект MarschalledObject, который передается слушателю с каждым
событием (может быть null) и значение типа long, которое задает длительность
подписки для слушателя. В архитектуре Jiro темы используются для классификации
событий и доставки событий только корректным подписчикам. Темой является
ограниченная точками строка, которая определяет древовидную структуру. Эта
структура помогает идентифицировать типы и подтипы событий.
Классификация типов событии начинается с корня ".".Точка представляет собой
корень древовидной структуры. Все элементы имеют в качестве прародителя
корень, поэтому все темы соответствуют строке ".". Строка ".А" представляет тему А.
Для определения подтипа для данной темы необходимо добавить точку и имя
подтемы в строку справа. Приведем два примера подтем для темы ".А": ".А.аа" и "A.bb".
Если подписчик на событие зарегистрирован сервисом событий в качестве
слушателя-наблюдателя темы ".A.bb", то подписчик на событие будет получать только
события ".A,bb" и его подтипы.
Объект события, переданный сервису событий, должен расширять класс Event
(пакет javax.fma.services.event). Класс PrinterErrorEvent .(рис. 6.7) расширяет
класс Event. Наследники класса Event должны реализовывать метод clone, чтобы
сервис событий мог копировать объекты-события и передавать их всем
подписанным слушателям (строки 18-21).
1 // PrinterErrorEvent.Java
2 // Это* класс определяет события, возбуждаемые принтером
3 package com.deitel.advjhtpl.jiro.DynamicService.printer;
4
5 // Пакет Jiro
6 import javax.fnta.services.event.Event;
7
8 public class PrinterErrorEvent
9 extends Event implements Сloneable {
10
11 // конструктор PrinterErrorEvent
12 public PrinterErrorEvent( Object source, String topic )
13 {
14 super ( source, topic );
15 )
16
17 // ошибка при клонировании
18 public Object clone(J
19 {
20 return new PrinterErrorEvent( source, getTopicf) );
21 J
22 )
Рис. 6.7. Класс ошибок, возбуждаемых моделью принтера
Класс Printer (рис. 6.8) несколько отличается от реализации класса PrinterSi-
mulator в пятой главе. Класс Printer является примером класса, публикующего
события в сервисе событий. Метод fire Event (строки 308-328) публикует все
события, порождаемые принтером. В строках 314-317 создается объект
PrinterErrorEvent (рис. 6.7), кроме того осуществляется его инициализация. Первым
параметром в PrinterErrorEvent является источник. Мы сделали так, что источник
описывается строкой с указанием, где порождено событие, дополненным информацией,
зависящей от типа события. Корнем является ".Prmter.Error". Потомками могут
Jiro
261
являться OutOfPaper, LowToner или Pa per Jam. Источником события может быть
любой объект кроме nail (строка 320). Метод post принимает один параметр —
событие, доставляемое слушателям.
1 // Printer.java
2 // Этот класс является моделью принтера
3 // Пакет Deitel
4 package com.deitel.advjhtpl.jiro.DynamicService.printer;
5
6 // Базовые пакеты Java
7 import Java.util.Stack;
8 import java.rmi.*,-
9 import java.io.*;
10
11 // Пакеты Jiro
12 import javax.fma.services.ServiceFinder;
13 import javax.fma.services.event.EventService;
14 import javax.fma.util.*;
15
16 public class Printer implements Runnable {
17
18 private Stack printerstack « new Stack();
19 private boolean isPrinting = false;
20 private boolean isPaperJam = false;
21 private boolean isOnline = true;
22
23 //50 листов вуыаги в лоФке
24 private int paperInTray = 50;
25
26 // 100% тонера
27 private int tonerCartridge = 100;
28
29 private String currentPrintJob;
30 private boolean ieAlive = true;
31
32 private EventService eventService;
33
34 // конструктор Printer
35 public Printer()
36 (
37 // получение сервиса событий домена управления
38 try (
39 eventService = ServiceFinder.getEventService(J;
40 )
41
42 // обработка исключений при получении сервиса событий
43 catch { Exception exception ) (
44 Debug.debugException(
45 "getting EventService*', exception );
46 )
47 1
48
49 // прекращение выполнении программного потока
50 public void etop()
51 (
52 isAlive = false;
}
// основной жизненный цикл принтера
// печать заданий на очереди заданий на печать
// 1) если в автономном режиме, то ожидание,
// 2) если в оперативном режиме, обработка задания на печать
public void run()
1
// основной цикл в программном потоке
while ( isAlive ) {
// принтер выводится в автономный режим
if ( Гi«Online ) {
synchronized ( this ) {
// ожидание перевода в оперативный режим
try (
wait (J ;
)
// обработка исключения ори ожидании
catch ( InterruptedExcaption exception ) (
Debug.debugExcaption(
"printer wait", exception );
)
) // завершение synchronized
1 //
// печать задания из очереди
BtartPrintingProcess();
цикла while
90 // запуск процесса начаты
91 private synchronized void etartPrintingProcesaQ
92 (
93 // разогреваем принтер, печатаем задачу ив очереди,
94 // определяем количество бумаги и тонера
95 try {
96
97 // разогреваем принтер для печати заданий иэ очереди
98 Thread.eleep( 1000 * 2 );
99
100 if ( isOnline &£ ( paperlnTray > О ) SS
101 ( tonerCartridge > 10 J (( ( UaPaperJam ) ) {
102
103 // начинаем процесс печати
104 currentPrintJob = getNextPrinbJob(J;
105 jbPrinting = true;
106
107 // 12 секунд для i
108 Thread.aleep( 1000 '
109
110 // а каждом задании иа печать 10 страниц
111 updatePaperlnTray( paperlnTray - 10 );
112 updateToner(J;
113 updatePaperJam(J;
114 isPrinting = false;
115
116
117
118
119
120 )
121
122 // обработав исключений при начале печати
123 catch( InterruptedException exception ) {
124 Debug.debugException(
125 "starting printing process", exception );
126 }
127
128 } // завершение метода startPrintingProcese
129
130 // возврат текущего задания на печать
131 private String gatCurrentPrintJob()
132 (
133 return currentPrintJob;
134 1
135
136 // наменяем количество бумаг™ в лотка принтера
137 private synchronized void updatePaperlnTray( int newValue )
138 (
139 paperlnTray = newValue;
140
141 // возбуждаем событие, если бума™ слишком мало
142 if ( paperlnTray <= 0 J (
143 System.out.println( "Printer: out of paper. " );
144 fireEvent( "OutofPaper" J ,-
145 J
146 )
147
148 // бумага замята?
149 public boolean iePaperJam()
150 (
151 return iePaperJam;
152 )
153
154 // печатает и» принтер?
155 public boolean i»Printing()
156 (
157 return isPrinting;
158 }
159
160 // принтер в оперативном ]
161 public boolean isOnline()
162 {
163 return i«Online;
164 )
165
166 // возвращает число листов бумаге в лотке
167 public synchronized int getPaperlnTrayO
168 {
169 return paperlnTray;
170 J
171
172 // изменяет объем тонера в картридже
173 public synchronized void updateToner(J
174 (
175 // посла выполнения задания уровень тонера уменьшается на 1%
176 tonerCartridge = tonerCartridge - 1;
177
178 // возбуждемка события при низком уровне тонера
179 if ( tonerCartridge <= 10 J (
180 System.out.println( "Printer: low tonar. " );
181 fireEventf "LowToner" ) ;
1S2 }
183 }
184
185 // устраняем замятие бумаги
186 public eynchronized void updatePaperJam()
187 {
188 if ( Math.random() > 0.9 ) {
189 iaPaperJam = true;
190 System.out.println( "Printer: paper jam. " );
191 fireEvent( "PaperJam" );
192 J
193 }
194
195 // возвращает количество тонера в картридже
196 public synchronized int getTonerf)
197 (
198 return tonerCartridge;
199 )
200
201 // увеличееме числа листов бума™ в лотке принтера
202 // до заданного значения
203 public void replenishPaperTray ( int paperStack )
204 {
205 System.out.println( "Printer: adding " + paperStack
206 + " pages to printer ... \n" J;
207 updatePaperInTray ( paperlnTray + paperStack ) ;
208 )
209
210 // генерация случайного числа заданий
211 private synchronized void populatePrintStack()
212 {
213 int numOfJobs = ( int ) ( Math.random ( ) * 10 J + 1;
214
215 // генерация заданий на печать
216 for ( int i = 0; i < numOfJobs ; i++ ) {
217
218 synchronized ( printerStack ) (
219 printerStack.add ( "PRINT_JOB_ID #" + i );
220 )
221 }
222 )
223
224 // добавление тонера
225 public synchronized void addToner()
226 (
227 Systen.out.printlnt "Printer: adding toner ... \n
228 tonerCartridge = 100;
229 }
230
231 // снятие заданий в очереди на печать
232 public synchronized void cancelPendingPrintJobs(>
233 {
234 synchronized ( printerStack ) (
235 printerStack.clear();
236 )
237 J
238
239 // возврат задания из очеред*,
240 // пополнение пустой очереди
241 private synchronized String getHextPrintJob()
242 {
243 if ( printerStack. isEmptyO ) {
244 populatePrintStack ( );
245
246 // моделирование отсутствия заданий
247 try (
248 Thread.sleep (
249 ( int J ( Math.random() * 1000 * 10 ) J ;
250 }
251
252 // обработка исключений
253 catch ( InterruptedException exception ) {
254 Debug.debugException(
255 "getting next print job", exception );
256 }
257 1
258
259 // Удаление задачи из очереди
260 String nextJob;
261
262 synchronized ( printerStack ) {
263 nextJob = ( String ) printerStack.pop();
264 }
265
266 return nextJob;
267
268 } // завершение метода getHextPrintJob
269
270 // возврат всех заданий из очереди
271 public synchronized String!] getPendingPrintJobs()
272 {
273 String[) pendingJobs;
274
275 // создание массива из заданий в очереди
276 synchronized { printerstack ) {
277 Object!] ten?» = printerStack.toArrayO ;
278 pandingJobs = new String! temp.length } ;
279
280 for ( int i » 0; i < pendingJobs.length ; i++ ) (
281 pandingJobs [ i ) = ( String ) temp[ i );
282 )
283 }
284
285 return pendingJobs;
286 }
287
288 // перевод принтера а оперативный режим
289 public void aetOnline()
290 (
291 System.out.println( "Printer: setting online ... \n"
292 isOnline = true;
293
294 // уведомление веек ожидающих
295 synchronized ( this ) (
296 notifyAllO ;
297 J
298 }
299
300 // перевод принтере в автономный режим
301 public void «etOfflineO
302
303
304
305
306
307 // возбуждение события
308 private void fire£vent( String error )
309 (
310 // передана события сервису событии
311 try {
312
313 // определение события
314 PrinterErrorEvent event = net* PrinterErrorEvent(
315 "com.deitel.advjhtpl.jiro.DynamicService.printer.■
316 + "ErrorHeeeage=" + error,
317 ".Printer.Error." + error );
318
319 // передача события
320 evantService.post( event );
321 )
Э22
323 // обработка исключений при передаче событий
324 catch { Exception exception ) {
325 Debug.debugException( "posting event-, exception );
326 )
327
328 ) // конец метода fireEvent
329 )
Рис. 6.8. Реализация модели принтера
Jiro
267
6.6.3. Сервис регистрации
Сервис регистрации осуществляет запись информации о важных событиях,
включая запуск приложения и возникновение ошибок. Сервис регистрации
обладает методами log u search для осуществления доступа и записи в журнал.
Сообщения, записываемые в журнал, должны быть объектами ja vax. fma.Bervice.log. Log -
Message. Сервис регистрации может быть настроен на использование различных
кодировок символов и локален. Клиент или сервис может передать или запросить
у сервиса регистрации сообщение, соответствующее определенному критерию по-
Класс PrinterManagementlmpl (рис. 6.5) использует сервис регистрации для
записи информации обо всех событиях, получаемых от принтера. Этот класс
использует ServiceFuider для получения посредника сервиса. В строке 64 Printer-
Man agemeotImp 1 получает сервис регистрации с помощью вызова метода Service-
Finder. getLogServiсе без параметров.
Запись в журнал содержит локализованное сообщение (сообщение в формате,
которое приложение может преобразовать в читаемый формат для данной л окали),
категорию для указания типа записи и исключение Throwble, возбуждаемое если
сообщение создается в условиях ошибочной ситуации. Категория сообщения
должна быть ограниченной точками строкой, которая начинается с одной из
следующих стандартных категорий: LogMes sage. AUDIT, LogMessage. DEBUG, LogMes-
Bage.WARNING, Log Message. INFO, LogMessage. ERROR, LogMessage. TRACE.
В строках 309-311 PrinterManagementlmpl создается локализованное сообщение.
Конструктор LocalizableMessage (пакет javax.fma.util) принимает четыре
параметра: Class, представляющий файл ресурса локализации, строка с ключом
сообщения, массив сериализуемых объектов и л окал ь (объект Locale), используемая
для создания текста сообщения. Объекты Class и String, передаваемые Loca-
ЦваЫеMessage, не могут принимать значения null, в противном случае будет
возбуждено исключение IllegalArgumentException. Как сериализуемый массив, так
и объект Locale могут быть null. Оба эти параметра имеют специальное
назначение, которое не обсуждается в описании данного примера: массив Serial Izable
служит для хранения типов объектов в сообщении1, объект Locale помогает указать
формат языка сообщения. Если клиент не может преобразовать
LocalizableMessage в текущую локаль, то создается сообщение об ошибке с использованием
текущей л окал и. Если клиент передает null в качестве параметра Locale,
используется умалчиваемая локаль. В данном примере в качестве корня для файла
ресурса указывается класс PrinterManagementlmpl.cl3as, а в качестве ключа
сообщения "TurnOnPrinter". Файл свойств, ассоциируемый с LocalizableMessage,
должен существовать и находиться в соответствующем каталоге. Далее приводятся
краткие указания по созданию файла свойств. Файл свойств должен содержать
пару "ключ-значение" с "TurnOnPrinter" в качестве ключа сообщения.
Сообщение создается в строках 314-316 PrinterManagementlmpl. Конструктор
LogMessage (пакет javax.fma.service.log) принимает три параметра: объект Loca-
lis able Message, содержащий сообщение, строку с категорией сообщения и объект
Throwable, содержащий хранимое исключение. Перечисленные параметры не
могут принимать значение null. Параметр Throwable может принимать значение
null. Класс указывает категорию сообщения, например, TRACE.printer. sou гее, где
source является источником события. В строке 320 PrinterManagementlmpl
осуществляется передача сообщения сервису регистрации с помощью вызова метода
log интерфейса LogService.
Содержимое сериализованного массива заменяет числа в фигурных скобках (т.е. {0}, {1),
(2}) в шаблоне сообщения, получаемого из файла свойств.
268 _____ Глава 6
Перед выполнением приложения, использующего сервис регистреции,
необходимо задать файл ресурса. Файл ресурса для PrinterManagementlmpl должен
иметь имя PrinterManagementbnpl.properties. Этот файл должен храниться в
каталоге ресурсов, где расположен PrinterManagementlmpl.class. В нашем примере
PrinterManagementlmpl.class расположен в
С:\com\dteitel\advjhttpl\ji-o\Dyn_micS*rvic«\service
так что файл PrinterManagementlmpl.properties должен быть расположен в ката-
С:\com\deitel\advjhttpl\jiro\Dyn»jnicService\eervice\reSources
Файл ресурсов должен содержать пары ключСообщения—текстСообщения. На
рис. 6.9 приведен файл ресурсов, используемый в приложении РгinterManage-
1 Event: = (0) «vent occurred on {1).
2 TurnOffPrinter = Turn off the printar.
3 ГигпОпРгinter = Turn on the printer.
Рис, 6,9 Файл PrinterManagementlmpl.properties
В состав Jiro SDK входит утилита viewlog для просмотра журнала сообщений.
Для запуска viewlog достаточно в командной строке ввести:
viewlog -domain jiro:JTROTEST
Данная командная строка запускает утилиту viewing, которая отображает
сообщения для домена jiro:JIROTEST. На рис. 6.32 приводится графический
пользовательский интерфейс утилиты. Информацию об использовании утилиты можно
получить в документе Jiro Technology SDK Programmers' Reference в подкаталоге
docs каталога, в который произведена установка Jiro SDK. Документ доступен как
в формате PDF (install_COnfig.pdf), так и PostScript (iostall_config.pB).
j^jb Типичная ошибка программирования 6.1
|^р| Класс LocalizableMessage возбуждает исключение javax.fma.util.Locali-
^^ zableMessage.LocalizationError. если отсутствует файл ресурса в
заданном каталоге или файл ресурса содержит некорректный ключ сообщения.
® Общая методическая рекомендация 6.3
Если перед запуском Jiro установить флажок Clean before start, то
предшествующие записи в журнале будут стерты.
6.6.4. Сервис планирования
Сервис планирования планирует задачи, которые необходимо выполнить в
будущем. Сервис планирования обладает методами для планирования запуска задач
в определенное время, периодически через указанное время или периодически по
определенным календарным датам.
Класс PrinterManagementlmpl (рис. 6.5) использует сервис планирования для
отключения принтера по пятницам в 20:00 и включения по понеданьникам в 7:00.
В строках 81-82 осуществляется получение ссылки на сервис планирования. Все
задания создается с помощью вызова фабричного метода сервиса планирования.
В строках 36-102 создаются два задания, запускаемые по определенным
календарным датам. PrinterManagment выполняет две задачи: отключение принтера по
пятницам в 20:00 и его включение по понедельникам в 7:00. Эти задачи связаны
JifO
269
с двумя отдельными событиями. В обоих случаях метод
newRepeatedDateSchedule планирует события. Метод newRepeatedDate Schedule прижимает восемь
параметров: объект Date, задающий время и дату, когда задача будет выполнена
впервые, объект Date, задающий время и дату, после которой задача выполняться
не будет, массив int, указывающий дни недели, когда будет выполняться задача,
массив int, указывающий часы (от 0 до 23), массив int. указывающий минуты (от О
до 59), и объект TimeZone, задающий часовой пояс. Если хотя бы один из
параметров равен null, то возбуждается исключение Illegal Argument Except ion.
Метод newRepeatedDateSchedule возвращает объект Schedule. В строках 102-106
и 160-162 PrinterManagementlmpl создается описание сообщения для каждого
пункта плана. Эти описания передаются методу scheduleTask для планирования
задачи. Метод scheduleTask принимает пять параметров: задачу, которую
реализует RemoteE vent Listener, объект Locaiaz able Message, описывающий задачу,
объект Schedule, описывающий условия запуска задачи, целое число, связанное
с политикой запуска, и объект MarshalledObject, который сервис планирования
передает слушателю задачи, когда возбуждается планируемое событие. В нашем
случае MarshalledObject, представляет собой строку с описанием задачи, которую
выполняет динамический сервис. Задача, описание задачи и план ее запуска не
могут быть равны null, в противном случае возбуждается исключение IllegalArgu-
ment Except ion. Политика, передаваемая методу, определяет задачи, которые
должны быть выполнены при повторном запуске сервиса планирования,
например, при останове системы или другим причинам, из-за которых сервис не может
корректно функционировать. Имеются три возможности для задания политики:
SchedulingService.NONE обусловливает отсутствие каких-либо действий при
повторном запуске, Scheduling Service. ONE и ScheduliugService.ALL. В случае
задания ScheduIingService.ONE запуск планируемых задач осуществляется однажды,
а при ScheduliugService.ALL — при всех последующих перезапусках сервиса.
Метод ScheduleTask возвращает объект Ticket, который можно использовать позже
для снятия задач.
Слушатель, реализующий интерфейс RemoteE vent Listener, требуется для
планирования задач. В заданное время сервис планирования вызывает метод notify,
выполняющий или делегирующий выполнение полученных задач. Класс Printer-
EventListener (рис. 6.6) в данном примере является реализацией RemoteEvent-
List en ег.
6.7. Развертывание динамических сервисов
Входящие в состав Jiro утилиты командной строки позволяют выполнять
различные задачи развертывания динамических сервисов.
Первым шагом по развертыванию динамического сервиса является генерация
посредника для него. Генерация посредника создает объект точки входа, который
удаленно доступен клиенту. Посредник также ответственен за создание
экземпляра динамического сервиса при каждом вызове конструктора посредника. В Jiro
имеются два инструментальных средства для генерации посредников для
динамических сервисов: jiroc и jirocw (находятся в подкаталоге bin каталога, где был ус-
таповлсп Jiro SDK), jiroc обладает графическим пользовательским интерфейсом,
тогда как jirocw запускается из командной строки для генерации посредника для
динамического сервиса. Для генерации посредника с помощью jirocw прежде всего
необходимо откомпилировать интерфейс и реализацию динамического сервиса
(PrmterManagement.java и PrinterManagementlmpl.Java). Данные примеры были
откомпилированы под Windows 2000. Инструкции по компиляции для других
операционных систем можно найти в документации по Java. Предполагается, что Jiro
SDK установлен в каталоге c:\jirosdk.
270
Глава 6
Необходимо также задать значение переменной окружения JIRO_CLASS-
РАТН. Эта переменная содержит пути к файлам JAR, которые расположены в
каталоге JiroSDK\lib: jini-core. Jar, jini-ext.jar и jLro-tools.jar. В среде операционной
системы Widows 2000 необходимо в командной строке выполнить следующую
команду set:
set JlRO_CIASSPATH=
o:\jiroadk\lib\jini-core.jar;
c:\jiroedk\lib\jini-ext.jar;
c:\jirosdJc\lib\jiro.jar;
e:\jirosdJc\lib\jiro-tools.jar
[Примечание. Можно задать постоянное значения данной переменной окружения,
чтобы каждый раз не вводить команды в командной строке]. Для компиляции
нашего примера необходимо сделать следующее:
javac -classpatb .;%JIRO_CIASSPATH% com\deitel\
advjhtpl\jiro\DynamicService\ooiBmon\*.Java
javac -classpatb .;%JIRO_CLASSPATH% com\deitel\
advjhtpl\jiro\DynamicService\printer\*.java
javac -classpatb .;%JIRO_CLASSPATH% com\deitel\
advjhtpl\jiro\DynamiсService\service\*.Java
В результате появится класс Printer Management.class. Затем из командной строки
сгенерируйте посредник, являющийся точкой входа для динамического сервиса1:
jirocw com.delta1.advjhtpl.jiro.DynamicSarvice.service.
Prin terManagement
Результирующий файл (PrinterManagementlmplProxy.java) расположен в
каталоге com\deitel\advihtpl\jiro\DynamicService\service. Далее откомпилируйте
сгенерированный посредник динамического сервиса с помощью команды:
javac -classpatb .;%ЛRO_CLASSPATH% com\deitel\
advj htpl\j iro\Dynamiс Servi ce\serviсе\
PrinterHanagemantlropl Proxy. java
PrinterManagement осуществляет подписку PrinterEventListener в качестве
слушателя-наблюдателя, так что он должен реализовывать RemoteEventListener,
который использует RMI для доставки событий. Сгенерируйте заглушку посредника
для RemoteEvent Listener следующим образом:
rmic -classpatb .;%JIBO_CLASSPATH% com.deitel.
advjhtpl.jiro.DynamicSarvice.service. PrinterEventListener
Следующий шаг включает в себя создание JAR-файла развертывания, который
включает в себя файлы классов нашего динамического сервиса, а также
соответствующие файлы свойств. JAR-файл мы назовем Printer Management Service, jar,
для его создания необходимо сделать следующее:
jar -cvf PrinterKanagementService.jar
com\deitel\advjhtpl\jiro\DynamicService\comiion\*.claaa
сom\d*ital\advjhtpl\jiro\DynamicSarvice\printer\*.class
com\deitel\advjhtpl\jiEo\DynamicS*rvice\service\*.class
com\deite1\advjhtpl\jiго\DynamiсService\cocaaon\reaourcea\
*.properties
1 Корневой каталог (в данном случае c:\j должен быть включен в переменную окружения
CLASSPATH, так как в утилите jirocw отсутствует параметр, позволяющий задавать
Jiro
271
Мы также должны создать интерфейсный JAR-файл. Интерфейсный JAR-
файл должен содержать все интерфейсы, реализуемые динамическим сервисом,
а также классы, от которых зависят эти интерфейсы:
jar -cvf PrinterManagementSarvice-ifс.jar
com\deitel\advjQtpl\jiro\DvnamicService\service\
PrinterManagement.class
com\de±tel\advjhtpl\jiro\DvnamicService\service\
Priii terHanagemantXmplProxy.class
Мы также должны создать JAR-файл загрузки. JAR-файл загрузки должен
содержать классы, которые клиент динамического сервиса должен загрузить по сети:
jar -cvf PrinterHanagementServica-dl.jar
com\deitel\advjhtpl\jiro\DynamicServica\sarvice\
PrinterManagemant.class
com\deitel\advjhtpl\jiro\DynainicService\aervlce\
PrinterHanagementImplProxy.class
Наконец, нам необходимо создать JAR-файл реализации. JAR-файл
реализации содержит файлы, необходимые для работы динамического сервиса,
jar -cvf PrintexManagementService-impl. jar
com\deitel\advjhtpl\jiro\DynamicService\coiamon\*.class
com\deitel\advjhtpl\jiro\DynamicServica\printer\*.clas3
com\deitel\advjhtpl\jiro\DynamicService\service\*.class
com\dei tal\advj htpl\j iro\DynamicService\service\resources\
*.properties
Теперь созданы все файлы, необходимые для работы утилиты jarpackw. Утилита
jarpackw создает JAR-файлы необходимые для развертывания динамического
сервиса. Для их создания необходимо:
jarpackw -pool %JIRO_CIASS_PATH%;
с:\PrintarHanagamantServica.jar
-ifc PrinterHanagementServica-ifc.jar
-impl PrinterHanagementServica-iimpi. jar
-dl PrintarHanagemantService-dl.jar
Опция —pool указывает путь к исходным и JAR-файлам, необходимым для
выполнения динамического сервиса. Опция —ifc указывает интерфейсный JAR-файл.
Опция -impl задает JAR-файл реализации. В свою очередь — dl задает JAR-файл
загрузки.
Последним шагом является запуск утилиты jardeploy, которая развертывает
динамический сервис в заданном домене:
jardeploy -station SharedJiroStation -domain domamName
-impl PrinterManagamantService-iimpl.jar
-dl PrinterHanagementService-dl.jar -verbose -inventory
здесь domainName представляет собой имя домена, в котором осуществляется
развертывание динамического сервиса. Опция командной строки —station задает имя
станции развертывания. В данном случае Shared Jiro Station является именем по
умолчанию, которое дается станции Jiro в процессе установки. Опция —domain
позволяет задавать домен управления, в котором функционирует станция. Опция
impl указывает JAR-файл реализации. Опция -dl указывает на JAR-файл
загрузки. Опция —verbose включает подробный режим вывода информации о процессе
развертывания динамического сервиса. Опция —inventory осуществляет вывод
информации о станции с развернутым динамическим сервисом.
Бели развертывание прошло успешно, Jardeploy отображает результаты
развертывания на панели Output утилиты Igniter. На рис. 6.10 приведены результаты
разве ртывания.
272
Глава 6
Рис. 6.10. Результаты развертывания сервиса
pspjfcj Типичная ошибка программирования
IgTJ Исключение DeploymentException с ключом "no_point_object8"
возбуждается, если не найдена точка входа в сервис.
6.7.1. Использование динамических сервисов
Процесс развертывания не приводит к созданию экземпляра динамического
сервиса. Сделать это можно с помощью посредника, сгенерированного утилитой
jirocw. Print erManagementStarter (рис. 6.11) представляет собой класс, который
создает динамический сервис PrinterManagenientlnipl.
1 // РEinterManagementStarter.java
2 11 Эфа программа демонстрирует получение посредника
3 // динамического сервиса.
4 package com.deitel.advjhtpl.jiro.DynamicServi.ee.client;
5
6 // Базовые пакеты Java
7 import Java.rrai.*;
8
9 // Пакет Jiro
10 import javax.fma.common.*;
11
12 // Пакет Deitel
13 import com.deitel.advjhtpl.jiro.DynaaicService.service.*;
14
15 public class PrinterManagemantStarter {
16
17 // конструктор PrinterManagemantStarter
18 public PrinterManagemantStarter( String domain ) {
19
20 PrinterManagement managementProxy;
21
22 // задание менеджера безопасности
23 if ( System.getSacurityManagerO = null )
System.setSecurityManager{ new RMISecurityMimager() );
// получение адреса станции
StationAddress stationAddress =
new StationAddresst domain,
null, null, null, null, null, null, null );
// получение посредников динамических сервисов
// и их запуск
try {
managementProxy =
new PrinterManagementlmplProxy( stationAt drees );
)
II обработка исключений
catch ( RemoteException exception } (
exception.printstackTrace();
) // завершение конструктора PrinterManageraentStaiter
// метод main
public static void main( String argst] )
{
String domain = "";
// получение инеин домена
if ( args.length != 1 ) {
System, out.println(
"Osage: PrinterManagementStarter Domain" i;
System.exit( 1 );
}
nterManagementStarter printerHanageroentStartsr =
new PrinterManagemantStarter( domain );
) // завершение метода main
Рис. 6.11. Программа PrinterManagementStarter запуска динамическог сервиса
StationAddress определяет станцию, с которой необходимо с< гдиииться.
Конструктор StationAddress требует задания восьми параметров: ст роки, задающей
домен управления, в котором функционирует станция, строки, задающей роль
станции и шести других полей, унаследованных от Servicelnfo1. В строке 35
осуществляется вызов конструктора PrinterManagementlmplProxy. Во время
инициализации станция Jiro регистрирует заглушку посредники РН nte r Management-
Impl в сервисе обнаружения Jini. После этого все клиенты для доступа к Printer-
Managemcntlmpl могут использовать заглушку, ссылку на которую можно
Поля StationAddress используются для задания шаблонов посредников станции в
сервисе обнаружения Jini. Все поля, значения которых не равны null, требуют точного
сопоставления. Поля со значениями null сопоставляются с любыми значениями. Нужно
укало полей для точной идентификации.
получить у сервиса обнаружения. Откомпилируйте класс PrinterManagement-
Starter и запустите на выполнение, чтобы создать экземпляр динамического
сервиса PrinterManagement.
После инициализации динамического сервиса PrinterManagement ссылка на него
может быть получена от сервиса обнаружения Jini. Далее приводится
вспомогательный класс DynamicServiceFuider (рис. 6.12), который взаимодействует с сервисом
обнаружения Jini. Подробно сервис обнаружения Jini был рассмотрен в главе 3.
1 // DynamicServiceFinder.java
2 // Даяний класс осуществляет получение посредника
Ъ II динакичесхоРО сервиса.
4 package com.deitel.advjhtpl.jiro.DynamicService.common;
5
6 // Базовые пакеты Java
7 import java.rmi.*;
3 import java.io.*;
9
10 // Базовые пакеты Jini
11 import net.jini.core.entry.Entry;
12 import net.jini.core.lookup.*;
13
14 // Пакеты расширений Jini
15 import net.jini.discovery.*;
16 import net.jini.lookup.entry.Servicelnfo;
17
18 // Пакеты Jiro
19 import javax.fma.util.*;
20
21 public class DynamicServiceFinder
22 implements DiscoveryListener (
23
24 private int serviceaFound = 0;
25 private ServiceRegistrar[] registrars;
26 private Entry!] entries;
27
28 // конструктор DynamicServiceFinder
29 public DynamicserviceFinder (
30 String domain. Entry[1 serviceEntries )
31 (
32 System.satSecurityHanager ( new BHISecurityHanager () ) ,-
33
34 entries = serviceEntriea;
35 LookupDiscovery lookupDiscovery = null;
36
37 // поиск сервиса обнаружении Jini
38 try (
39 lookupDiscovery = new LookupDiscovery(
40 пен String[] ( domain 1 );
41 )
42
43 // параклат lOException
44 catch ( XOBxception exception 1 {
43 Debug.debugException(
46 "discover lookup service", exception );
«■> i
// добавление служителя
lookupDiscovery.addDiscoveryListener ( this );
synchronized ( this } (
wait();
)
)
// обработке исключений при ожидании подтверждения
catch ( Exception exception ) (
Debug.dabugException(
"wait for lookup service", exception );
)
) // завершение конструктора DynamicServiceFindex
// помех hoswx сернисов обнаружения
public void discovered ( DiacoveryEvent event )
(
// получение регистраторов посредников зфмх сервисов
registrars = event.getRegistrar*();
// «пробуждение» всех ожз
synchronised ( this ) (
notifyAllO ;
// поиск некорректных сервисов обнаружения
public void discarded( DiscoveryEvent event) {1
// получение посредкина динамического сервиса
public Object getservice()
<
// поиск сервиса обнаружения для получения посредника
try (
ServiceТеирЛ.ate template = new ServiceTeaplate (
null, null, entries );
Object service = registrars[ 0 1.lookup! teaplate );
return service;
95 // обработка исключений при получении посредника
96 catch ( Exception exception) (
97 Debug.debugException ( "getting proxy", exception ) ,-
9в )
99
100 return null;
101
102 ) // завершение метода getService
103 )
Рис. 6.12. Нахождение посредника динамического сервиса в сервисе обнаружения
Глава 6
Конструктор Dynamics erviceFinder (строки 29-66) использует протокол
многоадресного запроса для поиска сервисов обнаружения Jini. Конструктор
блокируется до тех пор, пока не будет найден первый сервис обнаружения Jini.
Блокирование осуществляется с помощью использования метода wait (строка 56). Метод
discovered (строки 69-70) вызывается, когда сернис обнаружения найден. В этом
методе вызывается метод notify All (строка 76), чтобы конструктор мог
продолжить выполнение. Метод geiService возвращает все сопоставленные записи
первого найденного сервиса обнаружения1.
Класс PrinterClientGUI (рис. 6.13) демонстрирует, как осуществляются
удаленные вызовы методов динамических сервисов. Наш конструктор (строки 48-160)
принимает единственный параметр, который задает домен управления. В строках
271-274 осуществляется регистрация слушателя KemoteEventListenerlmpl в
сервисе событий в качестве слушателя-наблюдателя. В строках 275-276 создается
менеджер обновления аренды, который обновляет аренду, предоставляемую
слушателю. RcmoteEventListenerlmpl получает все уведомления с темой
".Printer.Error". Заметим, что KemoteEventListenerlmpl представляет собой удаленный
слушатель, доступный как для клиентов, так и для станций Jiro. Это делает
ненужным динамическую загрузку классов по сети. В строках 66-75 определена верхняя
панель (панель состояния принтера) графического интерфейса пользователя.
В строках 78-110 определена средняя панель (панель кнопок), которая содержит
две кнопки: Check Status (Проверить состояние) и Cancel Pending Print Jobs
(Снять задания оа печать). В строках 113-122 определена нижняя панель (панель
событий) графического пользовательского интерфейса, на которой отображаются
события принтера. Б строках 135-158 определяется слушатель для окна вывода.
Когда окно закрывается, слушатель-наблюдатель освобождается и
запланированная задача снимается. Метод checkStatnsButtonAction (строки 163-226)
отображает состояние принтера на панели состояния после щелчка на кнопке Check
Status. В строках 175-178 вызываются методы посредника для получения
текущего состояния принтера. В строках 197-224 осуществляется получение
информации о процессе и ее отображение на панели состояния.
Метод cancelJobsBnttonAction (Строки 229-241) выполняется, когда
пользователь щелкает мышью на кнопке Cancel Pending Print Jobs. В строке 233
вызывается метод посредника cancelPen ding Print Jobs для снятия стоящих в очереди
заданий на печать.
Метод getPrinterManagementProxy (строки 244-260) получает ссылку на
динамический сервис Printer Management. Ссылка позволяет вызывать методы
динамического сервиса PrinterManagement.
1 // PrinterClientGUI.Java
2 // Это приложение демонстрирует, как получить посредник
3 // динамического сервиса, политики управления и
6 // вызывать методы динамического сервиса.
5 package com.deitel .advjhtpl. jiro.DynamicService.client ,-
6
7 // Базовые пакеты Java
8 import java.rmi.*;
9 import java.io.*,"
10 import java.awt.*;
11 import Java .aw-t.event, *;
12 import java.util.*;
В нашем примере в строке 90 делается предположение, что в сети имеется единственный
сервис обнаружения Jini. В некоторых случаях могут функционировать несколько
сервисов обнаружения Jini.
15 // Стандартные расширения Java
16 import javax.swing.*;
17
18 // Базовые пакеты Jini
19 import net.jini.core.lease.Lease;
20 import net.jini.core.event.*;
21 import net-jini.core.entry.Entry;
22
23 // Пакеты расширения Jini
24 import net.jini.lease.LeaseRenewalManager;
25 import net.jini.lookup.entry.*;
26
27 // Пакеты Jiro
26 import }avax.fma.common.*;
29 import javax.fma.services.*;
30 import javax.fma.services.event.EventService;
31 import com.sun.jiro.util.*;
32
33 // Пакеты Deitel
34 import com.deitel.advjhtpl.jiro.DynamicService.common.*;
35 import com.deitel.advjhtpl.]iro.DynamicService.service.*;
36
37 public class PrinterСlientGDI extends JFrame
38 implements RemoteEventLiatener (
39
40 private PrinterManagemant printerManagementProxy;
41 private JTaxtArea printerstatusTaxtArea =
42 new JTextArea(};
43 private JTextArea printerEventTextArea =
44 new JTextArea () ;
45 private Lease observerLease;
46 private LeaseRenewalManager leaseRenewalManager;
47
48 public PrinterClientGUI( String domain )
49 <
50 super С "JIRO Printer Management Example" );
51
52 // создание менеджера безопасности
53 if ( System.getSecurityHanag*r() = null )
54 System.setSecurityManager( new RMISecurityManager() };
55
56 // получение ссылки на посредник
57 printerHanagementProxy =
58 getPrinterManagementProxy( domain );
59
60 // подписка я сервисе событий s качества слушателя-шеблкщателя
61 aubscriherObserver( domain );
62
63 Container oontainer = gatContentPane();
64
65 // панель состояния
66 JPanel pr in ter Status Panel = new JPaneK);
67 printerStatuePanel.setPreferredSize(
68 new Dimension( 512, 200 ) ) ;
69 JScrollPane statusScrollРапа = new JSсrollPane<);
70 statusScrollPane.setAutoscrollsf true );
71 statusScrollPane.setPreferredSise(
72 new Dimension! 400, 150 } );
73 statusScrollPane.getViewport().«dd(
74 printerStatuaTextArea, null ) ,-
75 printerStatusPanel.add( statusScrollPane, null );
76
77 // панель кнопок
78 JPanel buttonPsnel = new JPanel();
7 9 buttonPanel.setPreferredSise(
80 new Dimension( 512, 200 ) );
81
82 // определение днйствнн для кнопки Check Status
83 JButton checkStatusButton =
64 new JButton( "Check Status" );
85 checkStatusButton.addActionListener(
86
87 new ActionListenerQ (
88
89 public void actionPerformed( ActionEvent event ) {
90 checkStatusButtonActionf event );
91 )
92 )
93 );
94
95 // определенна действия для кнопки снятая заданий на печать
96 JButton cancelJobsButton = new JButton]
97 "Cancel Pending Print Jobs" );
98 cancelJobsButton.addActionListener(
99
100 new ActionListener() (
101
102 public void actionPerfonned( ActionEvent event ) (
103 cancelJobsButtonAction( event );
104 )
105 }
106 ) ;
107
108 // добавление кнопок на панель
109 buttonPanel.addt checkStatusButton, null );
110 buttonPanel.addf cancelJobsButton, null ) ,-
111
112 // панель собмий
113 JPanel printerEventPanel = new JPanel ();
114 printerEventPanel.setPreferredSise(
115 new Dimension( 512, 200) );
116 JScrollPane eventsScrollPane = new JBcrollPana();
117 eventsScrollPane.aetAutoscrolls( true );
118 eventsScrollPane.setPreferredSise(
119 new Dimension( 400, 150 ) );
120 eventsScrollPane.getViewport().add(
121 printerEventTextAraa, null );
122 printerEventPanel.add( eventsScrollPane, null ) ;
123
124 // инициализация текста
125 printerstatu»TextArea.setText( "Printer Status: \n" ) ;
126 printerEventTextArea.setTextf "Event»: \n" );
127
128 // сборка панелей
129 container.add( printerStatuePanel, BorderLayout.NORTH );
130 container.add( printerEventPanel, BorderLayout.SOUTH );
131 container.add( buttonPanel, BorderLayout.CKHTER ) ;
132
133 // освобождение слушатели~наблкдателя и снятие
134 // запланированных задач при закрытии окна
135 addWindowListener (
136
137 new HindowAdapter0 {
138
139 public void windowClosing( windowEvent event )
140 {
141 // освобождение слушателя и снятие задач
142 try (
143 leaseRenewalHanager.remove{ obaerverLeaee );
144 )
145
146 // обработка исключений
147 catch ( Exception exception ) {
148 exception. printStackTrace () ,-
149 )
150
151 // i
152
153
154 ) // завершение невода windowClosing
155
156 } // завершение конструктора HindowAdapter
157
158 ); // завершение addWindowListener
159
160 ) // завершение конструктора PrinterHanagementGUI
161
162 // проверка состояния принтера
163 public void.chackStatusButtonActionf ActionEvent event )
164 {
165 boolean isOnline = false;
166 boolean isPaperJam = false;
167 boolean iaPrinting = false;
168 int paperRema ining = 0;
169 String[] pendingJobs = null;
170
171 // удаленно» управление принтером
172 try (
173
174 // проверка, находится ли принтер в оперативном режима
175 isOnline = printerHanagementProxy.ieOnline<);
176
177 // поверка замятия бумаги
178 isPaperJam = printerManagemantProxy.iePaperJam();
179
180
181 isPrinting ш printerManagementProxy.isFrintingQ;
182
183 // сколько осталось бумаги
184 paperRemaining = printerManagementProxy.getPaperlnTray();
185
186 // получение заданий ка печать
187 pendingJobs =
188 printerManagesientProxy.getPendingPrintJobs0;
1B9 }
190
191 // обработка исключений при вызове катодов
192 catch ( Exception елсерtion ) {
193 exception.printstaсkTrace(};
194 }
195
196 // информация о состоянии принтера
197 if ( iaOnline )
198 printerStatusTextArea.append(
199 "\nPrinter is ONLINE.\n" };
200 else
201 printerStatusTextArea.append(
202 "\nPrinter is OFFLINE.\n" 1;
203
204 // информации о замятии бумаги
205 if ( isPaperJam }
206 printerStatusTextArea.append( "Paper jammed.\n" );
207 else
208 printerStatusTextArea.append( "No Paper Jam.\n" );
209
210 // информация об условиях печати
211 if ( isPrinting )
212 printerStatusTextArea.append(
213 "Printer is currently printing.\n" };
214 else
215 printerStatusTextArea.append(
216 "Printer is not printing.\n" };
217
218 // информации о количестве бумага а лотке
219 printerStatusTextArea.appendf "Printer paper tray has "
220 + paperRemaining + " pages remaining.\o" };
221
222 // количество заданий в очереди
223 printerStatusTextArea.append( "Number of pending jobs:
224 + pendingJobs.length + "\n" };
225
226 } // завершение метода checkStatueButtonAction
227
228 // снятие заданий иа печать
229 public void cancelJobsButtonAction( ActionEvent event }
230 {
231 // снятие Заланкй ха печать
232 try (
233 printerManagementProxy.cancelPendingPrintJobs{};
234 1
235
236 // обработка исключений при снятии заданий
237 catch ( Exception exception) {
238 exception.printstackTrace 0;
239 }
240
241 } // завершение метода cancelJobsButtonAction
242
243 // получение посредника динамического сервиса
244 public Printer-Management getPrinterManagementProxy(
245 String domain }
246 {
247 Entry[l entries = new Entry[[ {
248 new ServicelnfoJ "PrinterManagementlmpl" ,
249 "Deitel Association, Inc.",
250 "Deitel Association, Inc",
251 "1.0", "Model 0", "0.0.0.1" }
252 1 ;
253
254 DynamicServiceFinder finder = new DynamicServiceFinder(
255
256
257 // возврат посредника динамического сервиса
258 return { PrinterManagement } finder.getService();
259
260 } // завершение катода getPrinterManagementFroxy
261
262
263 public void subscriberObserver( String domain )
264 {
265 // подписка на события принтера
266 try (
267 EventService eventService =
2 68 ServiceFinder.getEventService( domain };
269 // подписка в качестве слушателя-наблюдателя
270 // на определенное событие
271 RemoteEventldstener listener =
272 new RemoteEventListenerlmpM this );
273 observerLease = eventService.subscribeObserver{
274 ".Erinter.Error", listener, null, 10 * 60 * 1000 ):
275 leaseBenewalManager = new LeaseRenewalManager(
276 observerLease, Lease.FOREVER, null };
277 1
278
279 // обработка исключений при подписке на события
280 catch ( Exception exception } (
281 exception.printstackTrace();
282 )
283 }
284
285 // получение подтверждений
286 public void notify( RemoteEvent event )
287 [
288 String output = "\nEVENT: " + ( String } event.getSource()
289 + "\n";
290 SwingOtilities.invokeLater(
291 new TextAppender( printerEventTextArea, output ) );
292 )
293
294 // катод main
295 public static void main( String args[] )
296 (
297 String domain = "";
298
299 // получение домеша
300 if ( args.length != 1 ) {
301 Systern.out.println(
302 "Usage: PrinterClientGUI Domain" );
303 System.e»it[ 1 );
304 )
305 else
306 domain = arge[ 0 ];
307
308 PrinterClientGDI client = new PrinterClientGDI( domain );
309 client.eetSise[ S00, 500 );
310 client.setVi*ible( true };
311
312 ) // еавершекиа метода main
313
314 // TextAppender добавляет текст в JTextArea. Этот объект,
315 // реалмаукянй интерфейс Runnable, должен выполняться только
316 // с мспольеовакием методов SwingOtillties invokeLater или
317 // invokeAndHait, т.к. модифицирует действужжрм компоненты Swing.
318 private class TextAppender implements Runnable {
319
320 private String text;
321 private JTextArea textArea;
322
323 // конструктор TextAppender
324 public TextAppender( JTextArea area. String newText )
325 {
326 text
327 text
328 )
329
330 // отображекке нового текста ■ JTextArea
331 public void runt)
332 (
333 // добавление нового сообщения
334 textArea.append( text );
335
336 // перемещение курсора в конец messageArea, чтобы
337 // сдшить сообщение полностью видимым на экране
338 textArea.setCaretEoeition(
33» textArea.getText().length() ) ,-
340 )
341
342 } // аавервение внутреннего каасса TextAppender
Рис. 6.13. Графический пользовательский интерфейс консоли управления
Jiro
283
Нам нужно, чтобы PrinterCHentGUI получал уведомления при возбуждении
событий принтером. В строках 263-283 осуществляется подписка
слушателя-наблюдателя на события принтера. Сервис событий вызывает метод notify (строки
286-292) при возбуждении принтером событий. В строках 290-291 отображается
информация о событиях принтера в нижней панели приложения. В строках
318-342 определен внутренний private класс TextAppender, который добавляет
текст в контейнер Swing в многопотоковом окружении.
Компилируется PrinterCiientGUI.java и все связанные с ним файлы командой:
Javaс -clasapatb с:\;с:\jirosdk\lib\jiro.jar
con\deitel\advjhtpl\jiro\Dynai&icService\clientS*. java
Запуск Print «Management Starter для инициализации динамического сервиса
осуществляется так:
Java -op c:\;c:\jiroadk\lib\jiro.jar
-DJava.security.policy=policy.all
con.deitel.advjhtpl.jiro.DynamicService.client.
Pri nterHanageme nts ta rter
domamName
здесь domainName представляет домен, где развернут динамический сервис Prin-
terM a d ageme nt.
Теперь запустим консоль управления (PrinterClientGUI):
Java -ср с:\;с:\jiroedk\lib\jiro.jar
-Djava.security.policy=policy.all
con.deitel.advjhtpl.j iro.DynamicService.client.
PrinterClientGDl
На рис. 6.14 представлен вид консоли управления после нажатия пользователем
кнопки Check Status.
Рис. 6.14. Проверка состояния принтера
На рис. 6.15 представлено окно утилиты Igniter после того, как в лотке
принтера закончилась бумага.
На рис. 6Д6 изображено окно программы PrinterClientGUI после получения
уведомления об отсутствии бумаги в лотке принтера.
Рис. 6.15. Утилита Igniter отображает отсутствие бумаги в лотке принтера
Рис. 6.16. Программа PrinterClientGUI отображает ситуацию отсутстаия бумаги в лотке
принтера
Jiro
285
В данном примере для отображения информации об исключениях используется
метод debugException класса Debug. В Jiro включена утилита viewdbg и viewdbgw,
позволяющие получить доступ к файлам отладки. При возникновении исключения
в панели ошибок Igniter отображается положение и имя файла, содержащего
отладочную информацию. Обычно полный путь к файлам отладки имеет следующий
формат:
FHA_number\xnumber.debug
где number представляет собой число, идентифицирующее отладочное сообщение.
Для просмотра отладочного сообщения введите в командной строке:
viewdbg path\filename.debug
viewdbgw path\filename■debug
где path и filename представляют собой путь к файлу и имя файла, содержащего
отладочную информацию.
6.8. Политики управления
Разработанный нами динамический сервис может выполнять много задач. К
сожалению, он не может автономно обрабатывать события. Политики управления
определяют, как будут обрабатываться события, возникающие в сети. Политики
управления в Jiro представляют собой динамические сервисы. Разработчики могут
настраивать эти сервисы так, чтобы реагировать на события без вмешательства
системного администратора. С помощью определения политик управления только
системные администраторы могут реагировать на важные события, обработка
которых не может быть автоматизирована.
В нашем примере мы будем использовать следующую политику управления:
В принтере всегда должна быть бумага и тонер.
Политика управления должна состоять из задач, реагирующих только иа одно
событие. Выдвинутая ранее политика управления содержит две задачи: в принтере
всегда должна быть бумага и в принтере всегда должен быть тонер. Поэтому
представленную политику управления для принтера мы должны разбить на две политики:
В принтере всегда должна быть бумага.
и
В принтере всегда должен быть тонер.
Правильное определение политики дает системному администратору
инструмент для детального управления сетью. Более того, распределение политики
управления по наборам динамических сервисов позволяет системному
администратору замещать и расширять индивидуальные политики.
Наш пример управления принтером использует две политики, позволяющие
усовершенствовать систему управления сетью. Класс OutofPaperpolicylmpl определяет
политику «Б принтере всегда должна быть бумага». OutofPaperpoIicy (рис. 6.17)
представляет собой интерфейс динамического сервиса управления бумагой.
1 // OutofPaperPolicy.java
2 // Данный класс определяет интерфейс динамического сервиса.
3 package coe.deitel.advjhtpl.jiro.DynamicService.policy;
4
5 // Базовые пакеты Java
6 import java.rmi.*;
286
Глава Б
7 iaport java.util.*;
9 // Бааоаыа пакеты Jini
10 iaport net.jini.core.event.*;
11
12 public interface OutofPaperPolicy extend* RemoteBvantLiatener (
13
14 public void stopPolicy( ) throws RemoteException;
15 }
Рис. 6.17. Интерфейс OutofPaperPolicy
Класс OutofPaperpolicylmpl (рис. 6.18) реализует интерфейс OutofPaperpolicy.
В строках 65-68 осуществляется регистрация класса PrinterEvent Listener
сервисом событий в качестве ответственного слушателя. Метод subscribeResponsible-
Before принимает шесть параметров: тему события, на которую необходимо
реагировать, ответственный слушатель, перед которым необходимо разместить каш
ответственный слушатель (noil указывает на то, что наш слушатель будет помещен
в начало списка), слушатель, который мы пытаемся зарегистрировать, объект,
передаваемый слушателю каждый раз при возникновении события (может быть
nail), срок аренды. Метод возвращает объект аренды Lease, задающий
продолжительность времени, на которое сервис событий связан со слушателем. Чтобы
слушатель был активен дольше, чем разрешает сервис событий, необходимо
обновлять аренду, используя LeaseRenewalManager. В строках 70-71 осуществляется
обновление аренды для нашего слушателя, при этом длительность аренды задается
константой Lease.FOREVEK. Конструктор LeaseRenewalManager принимает три
параметра: ссылку на обновляемый объект аренды, желаемый срок истечения
аренды и слушатель аренды, который будет получать уведомления об
исключениях при обновлении аренды (может быть null).
Метод stopPolicy отвечает за останов операции. Клиенты могут вызвать метод
stopPolicy, чтобы прекратить выполнение динамического сервиса
OutofPaperPolicy. В строке 96 прекращается аренда объекта LeaseRenewalManager передачей
ему в качестве параметра объекта истечения аренды. Мэтод notify получает события
от Print Event Listener. В строке 121 осуществляется получение объекта-источника,
содержащего информацию, относящуюся к сущности, вызвавшей событие. В
строке 134 осуществляется проверка, является ли объект-источник экземпляром класса
String. Если его не так, то можно сделать вывод, что событие было возбуждено не
нашим принтером. В этом случае в строке 136 возбуждается событие NotHandled-
Exception, в результате чего сервис событий продолжает распространение события
в соответствии со своим внутренним списком слушателей. Бели объект-источник
является строкой, то осуществляется проверка содержимого строки (строки 143-144).
В строке 153 вызывается метод addPaper в точке входа динамического сервиса
PrinterManagement. В строках 156-172 осуществляется запись сообщения с
описанием действия, являющегося ответом на отсутствие бумаги в лотке принтера.
В строках 200-216 определен метод getPrinterManagementProxy, который
использует наш вспомогательный класс DynamicServiceFinder для поиска ранее
инициализированной заглушки посредника для динамического сервиса
PrinterManagement. В строках 219-228 определен метод getLooknpEntries, который требуется
точкам входа динамических сервисов.
1 // OutofPaperPolicy 1щ>1.java
2 // Обработка собихмй, возбуждаемых принтером
3 // с помощь» регистрации ответственного сдужателж
4 package com.deitel.advjhtpl.jircDyaanicSarvice.policy;
Jiro
6 // Базовые пакеты Java
7 import java.io.Serializable;
8 import java.rmi.*;
9 import java.util.*;
10
11 // Стандартные расширения Java
12 import javax. swing.*;
13
14 // Базовые пакеты Jini
15 import net.jini.core.event.*;
16 import net.jini.cere.entry.";
17 import net.jini.core.lease.*;
18
19 // Пакеты расширения Jini
20 import net.jini.lease.LeaseRenewalHanager;
21 import net.jini.lookup.entry.*;
22
23 // Пакеты Jiro
24 import javax.fma.services.ServiceFinder;
25 import javax.fma.services.event.*;
26 import javax.fma.services.log.*;
27 import javax.fma.util.*;
28 import javax.fma.common.*;
29 import javax.fma.server.*;
30
31 // Пакеты Deitel
32 import com. deitel. advjhtpl. jiro. Dynamic Service. service. *,-
33 import com.deitel.advjhtpl.jiro.DynamicService.common.*;
34
35 public class OutofPaperPolicyImp1
36 implements OutofPaperPolicy {
37
38 private Lease listenerLease;
39 private LeaseRenewalManager leaseRenewalManager;
40
41 private LogService logServiee;
42 private PrinterEventListener listener;
43 private PrinterManagement printerHanagewentProxy;
44
45 // Конструктор OutofPaperPolicylmpl
46 public OutofPaperPolicylmpl()
47 (
48 // подписка в качестве ответственного слушатели
49 listener = new PrinterEventListener( this };
50
51 // запуск политики управления OutofPaper
52 try (
53
54 // получение сспяни на точку ахода динамического сервиса
55 printerManagementProxy = getPrinterManagementProxy();
56
57 // получение ссылки на сервис регистрации
58 logService = ServiceFinder.getLogServicef);
59
60 // получение ссылки на сервис регистрации
61 EventService eventService =
62 Servi.ceFo.nder. getEventService [} ;
63
64 // подписка в качестве ответственного слушатели
£5 listenerLease =
66 eventService.subscribeResponsibleBefore(
б"? ".Printer. Error. OutofPaper", null, listener,
68 "OutofPaperEventLietener", null, Lease.FOREVER );
69
70 // обновление аренды
71 leaseRenewalManager = new LeaseRenewalManager(
72 listenerLease, Lease.FOREVER, null );
73
74 } // завершение блока try
75
76 // обработка исключений при запуске политики
77 catch ( Exception exception ) {
78 System.out.println( "OutofPaperPolicylmpl: " +
7 9 "Exception occurred when starting policy." );
80 System.out.println( "Please read debug file ... \n" );
81 Debug.debugException(
82 "starting LowTonerPolicy", exception };
83 1
84
85 System.out.println( "OutofPaperPolicylmpl: started." );
86
87 ) // завершение конструктора OutofPaperPolicylmpl
88
89 // останов OutofPaperPolicylmpl
90 public void stopPolicyO
91 <
92 // останов политики управления OutofPaper
93 try I
94
95 // истечение срока аренды
96 leaseRenewalManager.cancel( liatenerLease ):
97 System.out.println( "OutofPaperPolicylmpl: stopping." )
98 }
99
100 // обработка исключений после истечения срока аренды
101 catch ( Exception exception } {
102 System.out.println( "OutofPaperPolicylmpl: " +
103 "Exception occurred when canceling lease.'" );
104 System.out.println( "Please read debug file ... \n" J;
105 Debug.debugException(
106 "stopping OutofPaper policy", exception };
107 )
108 1
109
110 // получение уведомлений
111 public void notify( RemoteEvent remoteEvent )
112 throws UnknownEventException, RemoteExcaption,
113 EventNotHandledException
114 (
115 Object sourceobject = null;
116
117 // источник событий
118 try (
120 // получение источника событий
121 sourceObject » remoteEvent.getSource();
122 }
123
124 // обработка исключений при получении источника событий
125 catch ( Exception exception ) (
126 Systee.out.println( "OutofPaparPolicyImpl: " +
127 "Exception occurred when getting «vent source." );
128 Systee.out.println( "Please read debug file ... \n" );
129 Debug.debugException(
130 "getting event source", exception );
131 }
132
133 // событие не от принтера
134 if ( !( sourceObject instanceof String } } (
135
136 throw new EventHotHandledException() ;
137 }
138
139 // получааи значение строки
140 String source = ( String } sourceObject; ■
141
142 // проверяем источник события
143 if ( source.equals ( "com.deitel.advjhtpl.ji.ro."
144 +"DynanicService. printer .ErrorMessage=Outof Paper'') ) (
145
146 System.out.println( HOntfFaperPolicy: "
147 + "handling OutofPaperErant..." );
148
149 // действие
150 try (
151
152 // наполняем лоток бумагой
153 printerHanagementProxy.addPaper( 50 };
154
155 // создаем параметры сообщения для записи ж журнал
156 Serializable params[] =■ new Serializable[ 2 ];
157 params[ 0 ] = source;
158 paramsi 1 ] = aew Date (>;
159
160 // создаем локализованное сообщение
161 LocalIsableMessage local i «аЫеНе mm age =
162 пен LocalizableHese*ge(
163 OutofPaperPolicylmpl.class,
164 "Action", params. Locale.US );
165
166 // создаем сообщение
167 LogMessage logMessage = new LogMessage(
168 localizableHessage, LogHessage.TRACE
169 + ".OutofPeperEvent." + source, null );
170
171 // действие для записи сообщения
172 log5ervice.log( logMessage ) ,-
173
174 }
175
176 // обреботха исключении при посылке сообщения
177 catch ( Exception exception } (
178 System.out.println ( "OutofPaperPolicylmpl: " +
179 "Exception occurred when posting log message."
1BD System.out.println( "Please read debug file ...\n"
181 Debug.debugException( "log service", exception );
182 }
163
184 } II !
185
186 // событие не от i
187 else {
188
189 System.out.println( "OutfPaparPolicy: " +
190 " NOT handling OutofPaperEvent..." );
191
192 // требуется ответственный слушатель,
193 // когда событие не обрабатывается
194 throw new EventNotHandledException();
195 }
196
197 } II завершение метода notify
198
199 // получение посредников динамических сервисов
200 public PrinterManagement getPrinterttanagementProxy(}
201 {
202 Entry[] entries = new Entry[] (
203 new ServiceInfo( "PrinterManagementXopl",
204 "Deitel Association, Inc.",
205 "Deitel Association, Inc",
206 "1.0", "Model 0", "0.0.0.1" )
207 };
208
209 String domain = System.getProperty< "javax.fma.domain" );
210 DynamicServiceFinder finder =
211 new DynamicSarviceFinder( domain, entries ) ;
212
213 // воввращаеы посредник
214 return ( Printemanagement ) finder.getService(};
215
216 } II завершение метода getPrinterManagementProxy
217
218 // определяем класс как динамический сервис при развертывании
219 private Entry[] getLooltupEntries()
220 (
221 return < new Entry[] (
222 new Servicelnfо( "OutofPaperPolicylmpl",
223 "Deitel Association, Inc.",
224 "Deitel Association, Inc",
225 "1.0", "Model 0", "0.0.0.1" }
226 }
227 ) ;
228 )
229 } ^^^
Рис. 6.18. Реализация интерфейса Outof Paper Policy
Jiro
291
OatofPaperPolicylmpl требует файла свойств для сообщений, записываемых
сервисом регистрации. Обратитесь к разделу 6.6.3 относительно создании файлов
свойств. На рис. 6.19 приведено содержимое файла свойств.
1 Action = Added paper to (0} on (1>.
Рис. 6.19. Файл свойств OutofPaреrPolfcylmpl.properties для OutofPaperPolicylmpI
LowTonerPolicy (рис. 6.20) определяет интерфейс для второй политики
управления «В принтере всегда должен быть тоиер». Реализация этой политики
аналогична OatofPaperPolicylmpl (рис. 6.18). Разница заключается в информации,
записываемой сервисом регистрации и действиях при обработке событий. На
рис. 6.20 приведен интерфейс политики LowToner Policy, а на рис. 6.21 ее
реализация LowTonerPolicylmpl.
1 // LowTonerPolicy.Java
2 // Данный класс определяет интерфейс для динамического сервиса.
3 package com.deitel.advjhtpl.jiro.DynamicService.policy;
4
5 // Базовые пакеты Java
G import java.rmi.*;
7 import java.util.*;
8
9 // Базовые пакета Jini
10 import net.jini.core.event.*;
11
12 public interface LowTonerPolicy extends RemotaEventListener (
13
14 public void stopPolicyO throws RemoteException;
15 }
Рис. 6.20, Интерфейс политики управления для низкого уровня тонера в картридже
принтера
1 // LowTonerPolicylmpl.Java
2 // Обработка событий, генерируемых принтером, с помощью
3 // регистрации ответственного слушателя.
4 package com.deitel.advjhtpl.jiro.DynamicService.policy;
5
6 // Базовые пакеты Java
7 import java.io.Serialixable;
8 import java.rmi.*,"
9 import java.util.*;
10
11 // Стандартные расширения Java
12 import javax.awing.*;
13
14 // Базовые пакеты Jini
15 import net.jini.core.event.*;
16 import net.jini.core.entry.*;
17 import net.jini.core.lease.*;
18
19 // Пакеты расширения Jini
20 import net.jini.lease.LeaseRenewalManagar;
21 import net.jini.lookup.entry.*;
22
23 // Пакеты Jiro
24 import javax. fine.services.*;
25 import javax. fin*, services .event. *;
26 import javax.fma.sarvices.log.*;
27 import javax.fma.util.';
2B impost jsnrax-fina. common. *;
29 import javax.fma.server.*;
30
31 // Пакета Deitel
32 impost com.deitel.advjhtpl. jircDynamieSarvic*.service.*;
33 import com.deitel.advjhtpl.jiro.DynamicService.common.*;
34
35 public class LowTonerPolicylmpl
36 implements LowTonerPolicy {
37
3B private Lease liatenesLease;
39 private LeaseRenawalHanager leaseRenewelHanager,-
40
41 private LogService logService;
42 private PrinterEventListener listener;
43 private PrinterHanagement printerManagementProxy;
44
45 // Конструктор LowTonerPolicylmpl
46 public LowTonerPolicylmpl()
47 (
48 // подписка в качества ответственного слушателя
49 listener ■* new PrinterEventListener( this };
50
51 // запуск политихи управления LowTonar
52 try {
53
54 // получение ссылки на точку входа динамического сервиса
55 printerHanagementProxy «= getPrinterManagementProxy(};
56
57 // получение ссыпки на сервис регистрации
SS logService = ServieeFinder.getLogService();
59
60 // получение ссыпки на сервис регистрации
61 EventService eventService =
62 ServiceFinder.getEventService();
63
64 // подписка в качестве ответственного слушателя
65 listenerLease =
66 eventService.subscribeResponsibleBefore(
67 ".Printer.Error.LowToner", null, listener,
66 "LowTonerEventListener", null. Lease.FOREVER );
69
70 // обноаиекие аренды
71 leaseRanewalManager = new LeaseRanewalHanager(
72 listenerLease, Lease.FOREVER, null );
73 >
74
75 // oopa6owica исключения при запуске политики
76 catch ( Exception exception ) {
77 System.out.println( "LowTonerPolicylmpl: " +
78 "Exception occurred when starting policy." );
79 System.out.println( "Please read debug file ... \n" );
8 0 Debug.debugException(
81 "starting LowTonerPolicyXmpl", exception ) ;
82 }
83
84 System.out.println( "LowTonerPolicyXmpl: started." );
85
86 } // завершение конструктора LowTonerPolicyXmpl
87
BB // останов OutofPaperPolicy
B9 public void stopPolicyO
90 (
91 // останов политики управления LowToner
92 try (
93
94 // истечение срока аракжы
95 leaseRenewalManager.cancel[ listenerLease );
96 System.out.println( "LowTonerPolicyXmpl: stopping." ),
97 }
9B
99 // обработка исключения ери истечения срока аренды
100 catch ( Exception exception ) {
101 System.out,println( "LowTonerPolieylmpl: " +
102 "Exception occurred when canceling lease." );
103 System.out.println( "Please read debug file ... \n" },
104 Debug.debugException{
105 "stopping LotfTonerPolicylmpl" , exception ) ;
106 }
107 }
108
109 // получение уведомлений
110 public void notify( RemoteEvent remoteEvent }
111 throws DnxnownEventException, RemoteException,
112 EventMotHandledException
113 (
114 // источник собммк
115 Object aourceObject = null;
116
117 // получение источника событий
118 try {
119 sourceObject = remoteEvent.getSource(>;
120 )
121
122 // обработка исключений при получения источника событий
123 catch { Exception exception ) {
124 System.out.println( "LowTonerPolieylmpl: " +
125 "Exception occurred when getting event source." );
126 System.out.println( "Please read debug file ... \n" ),
127 Debug.debugException)
128 "getting event source", exception );
129 )
130
131 // событие не от нашего принтера
132 if ( !( sourceObject iastsneeof String ) } (
134 throw new EventMotBandledException(};
135 }
136
137 // получение строкового значения
138 String source = ( String ) aourceObject;
139
140 // проверка источника событии
141 if ( source.equals( "eom.deitel.advjhtpl.jiro."
142 + "DynamicService.printer.BrrorMessage=LowToner"
143
144 System.out.println(
145 "LowTonerPolicylmpl: handling LotfTonerEvent..,
146
147 // действие
148 try (
149
150
151
152
153 // генерация сообщения для сервиса регистрации
154 Serial!zable рагалш[] = new Serializable[ 2 };
155 рагавш[ 0 ] = source;
156 рагавш[ 1 } = пен Date();
157
158 // генерация локализованного сообщения
159 LocalizableHessage localizableHessage -
160 new LocalizableHessage(
161 LowTonerPolicylmpl.class, "Action",
162 params, Locale.US );
163
164 // генерации сообщения
165 Logttessage logMessage = new Logttessage[
166 localizableMessage, LogHessage.TRACE
167 ■* ".LowTonerEvent." + source, null };
168
169 // действие
170 logService.logf logMessage );
171
172 } // завершение try
173
174 // обработка исключений
175 catch ( Exception exception } [
176 System.out.println( "LowTonerPolicylmpl:" +
177 "Exception occurred when taking action." );
178 System.out.println( "Please read debug file...
179 Debug.debugException( "take action", exception
180 }
181
182 } II завершение блока if
183
184 // событие не от навего принтера
185 else (
186 System.out.println( "LowTonerPolicylmpl:
187 + "ЯОТ handling OutofPaperEvent..." );
188
189 // требуется ответственный слушатель.
190 // когда событие не обрабатывается.
191 throw new EventNotHandledException{);
192 }
193
194 )
195
196 // получение посредников динамических сервисов
197 public PrinterManagement getPrinterManagementProxy()
198 (
199 Entry[] entries = new Entry[] {
200 new S«rvic*lnfo( "PrinterManagemantXmpl",
201 "Deitel Association, Inc.",
202 "Deitel Association, Inc",
203 "1.0", "Model 0", "0.0.0.1" )
204 } ;
205
206 String domain = System.getProperty( "javax.fma.domain" };
207 DynamicServicefinder finder =
208 new DynamicServiceFinder( domain, entries );
209
210 // возвращение посредника
211 return ( PrinterHanagement ) finder.getSarvice();
212
213 } // завершение метода getPrinterManagementProxy
214
215 // определяет класс как динамически* сервис при развертывании
216 private Entry[] getLookupEntries()
217 (
218 return ( new Entry [] (
219 new ServiceInfo( "LowTonerPolicylmpl",
220 "Deitel Association, Inc.",
221 "Deitel Association, Inc",
222 "1.0", "Model 0", "0.0.0.1" )
223 }
224 ) ;
225 )
226 } ____ _^
Рис. 6.21, Реализация интерфейса политики управлений для низкого уровня тонера
в картридже принтера
Класс LowTonerPolicylmpl также нуждается в файле свойств для записи
сообщений сервисом регистрации. Обратитесь к разделу 6.6,3 относительно создания
файлов свойств. На рис. 6.22 приведено содержимое файла свойств.
1 Action » Added toner to (0> on (1>.
Рис. 6.22. Файл свойств LowTonerPolicylmpl
6.8.1. Развертывание политик управления
Перед активацией политик управления мы должны развернуть их. Для данного
примера мы перезапустим Jiro, установив флажок Clean before Start. Это
гарантирует, что ранее запущенный динамический сервис PrinterManagement не
функционирует во врамя его повторного развертывания с новыми политиками управле-
ния. Нужно сделать ту же последовательность шагов, которая описана в
разделе 6.7 для обеих политик управления. Из-за зависимостей между динамическим
сервисом PrinterManagement и политиками управления мы будем развертывать
их вместе.
Развертывание системы управления подробно описано в разделе 6.7, нужно
выполнить следующую последовательность шагов;
1. Откомпилировать все файлы *.java в каталогах
com\deitel\adv jhtpl\j iго\DynamicServica\comnon\
com\deifcel\adv jhtpl\j iro\Dynamiс Se rvi ce\printer\
оom\deltel\advjhtp1\jlro\DynamicServiсе\servic*\
com\de i tel\advj htp1\j iго\DynamicServi ce\poli су\
2. С использованием утилиты jirocw создать файл .Java посредника для
следующих классов:
com.deitel.advjhtpl.jiro.DynamicService.service.
PrinterManageaentlmpl
com.deitel.advjhtpl.j iro.DynamicService.policy.
OutofPeparPo1icyImp1
com.deitel.advjhtpl. jiro.OynamiсService.policy.
LoitTone r to 1 i cy Impl
3. Откомпилировать исходные файлы посредника:
com\d*itel\edvjbtpl\jiro\DynamicService\service\
PrinterHanagementlmplProxy.Java
com\deitel\advjhtpl\jiro\DynamicS*rvice\pol±cy\
OutofPaparPolicyImplProxy _java
com\deitel\advjhtpl\jiro\DynamicService\policy\
LowTonerPolicyImplProxy.Java
4. Используя утилиту rmic, создать заглушку посредника для
Создайте PrinterManagement Service .jar, содержащий файлы,
перечисленные на рис. 6.23.
Каталоги Файлы
com\ delte1\advjhtp1\ji г c-\Dynami cServi се \сотвоп\
|DynamicServiceFinder.class
com\deitel\advjhtp1\jiro\DynamicServiceSprinter\
Printer.class
__
com\de i tel\advjhfcp1\j iго\Dynami cServica\service\
PrinterEventLietener.class
PrinterEventListenec Stub.class
PrinterManagement.class
Pri n tecHanagementlmpl.class
PrinterHsnagementlmplProxy.class
com\deite1\adwjhtp1\J iго\DynamiсService\вerviее\геsourcee\
|PrinterManagementImpl.properties
Jiro 297
Каталога
com\deital\advjhtpl\jir
[Файлы
о\DynamlcService\po1i су\
|LowTonerPolicy.class
LowTonerPolicy Impl.class
ILovTonorPolicylmplProxy.class
com\deit*l\adVjhtpl\jir
OutofPaperPolicy.class
OutofPsperPolicyImpl.class
Outof PaperPo1icyImplProsy.class
о\DynamicS*rvlce\po1icy\r«*ourсеа\
LowTonerPolicyImpl.properties
OutofPaperPolicyImpl.properties
Рис. 6.23. Содержимое архиве PrinterManagementServiccjar
6. Создать PrinterManagementService-ifc.}ar, содержащий файлы,
перечисленные на рис. 6.24.
com\ da i tel \ adv j h tpl \ j i г о \Dyn amicSe rvice \ s • rvi се \
PrinterManagement.class
PrinterManagementImplProxy.class
Рис. 6.24. Содержимое архива PrinterManagementServke-ifcjar
7. Создать PrillterManagementService-dl.jer, содержащий файлы, перечис-
com\daite1\advjhtp1\jiro\DynamicServiсе\service\
PrinterManagement. class
Pr interManagemantXmplProxy .class
Рис. 6.25. Содержимое архива PrlnterManagement5ervke-dl.Jar
8. Создать PrinterManagement Service-impl. jar, содержащий файлы,
перечисленные на рис. 6.26.
Каталоги \ Файлы
coa\dei tel\advj htp1\j iro\DynamicSarviса\сошдоп\
| DynamicServicaFinder.class
com\deltel\advjhtpl\j iго\DynamicServ i ce\printer\
Printer.class
PrinterErrorEvent.claas
298
Глава б
Каталоги ' Файлы
com\deitel\advjhtp1\jirо\Dynaniс Se rvi ca\aervice\
PrinterEventListenox,class
PrinterEventLiatener Stub.class
PrinterMaaagement. class
PrinterManagement_ Impl. class
PrinterHanagement-XmplPxoxy.class
com\deite1\a dvj btpl\jiro\DynamicService\sarvice\resourC*s 4
|PrinterManagaiMnt.Xmpl.properties
Com\dei tel\adv jhfcp 1\jiго\DynamicS»rvice\po1iey\
LowTonerPolicy.class
LowTonerPolicyImpl.class
LowTonerPolicyXmplProxy.class
OutofPaperPolicy.class
OutofPaperPolicyXmpl.class
OutofPaperPolicyImplProxy.class
com\deitel\advjhtpl\jiro\DynaniicService4policy\reaources\
LowTonerPolicyXmpl. properties
OutofPaperPolicyImpl. properties
Рис. 6.26. Содержимое архива PrInterManagementService-lmpl.jar
9. Запустить утилиту jarpackw с параметрами командной строки, как
показано на рис. 6.27.
Флаг
-p~i
-ifc
-impl
-dl
Значение
% JIRO_CIASSPATH%
С:\PrinterHanagenentService.jar
Print*rManagement£ervice-ifc.jar
PrinterManagementService'impl.jar
PrinterManagementService-dl.jar
Рис. 6.27. Параметры командной строки утилиты jarpackw
10, Развернуть динамические сервисы на станции Jiro с помощью утилиты
jardeploy и параметров командной строки, приведенными на рис. 6.28.
Флаг
-station
-domain
-impl
-dl
Значение
SharedJiroSta tion
domainName
PrinterManagenentServic*'Xmpl.jar
PrinterManagenentService-dl.jar
Jiro
299
Рис. 6.28. Параметры командной строки утилиты jar deploy
К настоящему моменту мы развернули две политики управления для
динамического сервиса PrinterManageinent. Теперь необходимо запустить динамический
сервис PrinterManageinent. Запустите PrinterManagementStarter (рис. 6.11) для
удаленной инициализации динамического сервиса PrinterManageinent. После
этого необходимо запустить развернутые политики. Класс PoIiciesStarter (рис. 6.29)
удаленно инициализирует сервисы OutofPaperPolicylmpI и LowTonerPoIicylmpI.
В строках 28-30 определен Station Address, где будут инициализированы
политики управления. В строках 34-37 инициализируются обе политики управления:
OutofPaperPolicylmpI и LowTonerPoIicylmpI.
1 // PoIiciesStarter.Java
2 // Это приложение демонстрирует, как получить посредник и
3 // политики безопасности.
4 package com.deitel.advjhtpl.jiro.DynamicService.client;
5
6 // Базовые пакеты Java
7 import java.rroi.*;
В
9 // Пакеты Jiro
10 import javax.fma.common.*;
11
12 // Пакеты Deitel
13 import com.deitel-«dvjhtpl.jixo.DynamicService.policy.*;
14
15 public class PoIiciesStarter {
16
17 // Конструктор PoIiciesStarter
18 public PolicieaSterter{ String domain ) {
1»
20 OutofPaperPolicy paperPolicyProxy;
21 LowTonerPolicy tonerPolicyProxy;
22
23 // задание менеджера беаопаошости
24 if { System.getSecurityManager() = null )
25 System.setSecurityManager{ new RMXSecurityManager{) );
26
27 // получение адреса станции
28 StationAddress stationAddress =
29 nev StationAddress( domain,
30 null, null, null, null, null, null, null );
31
32 // получение посредников политик управления
33 try {
34 paperPolicyProxy =
35 new OutofPaperPolicylmpIProxy{ stationAddress );
36 tonerPolicyProxy =
37 new LowTonerPolicyZmplProxy( stationAddress ),-
38 }
300
Глава 6
39
40 // обработка исключений
41 catch ( RemoteExeeption exception ) {
42 exception.printStackTraceO;
43 )
44
45 ) // завершение конструктора Pol ideas tarter
46
47 // метод main
48 public stetic void main( String arga[] )
49 I
50 String domain = "";
51
52 // получение ниени домена
53 if ( arge.length ?= 1 ) (
54 System, out.printlnf
55 "Usage; PoliciesStarter Domain" );
56 System.exit{ 1 );
57 )
58 else
59 domain ™ args[0 1;
£0
61 PoliciesStarter policiesStarter =
€2 пек PoliciesStarter{ domain );
63
64 ) // завершение метода main
65 }
Рис. 6.29. Утилита инициализации политик управления
Чтобы инициализировать наши динамические сервисы, необходимо:
1. Откомпилировать Printer Client GUI, PriiiterManagementS tarter и Polices-
Starter н каталоге com\deiteI\adhhtpl\jiro\DynaniicService\chent\
2. Запустить класс com.deitel.adhhtpl.jiro.DynamicServlce.cHent.PrinterMa-
nagementStarter с использованием файла политики policy .all. Нужно
также передать PrinterManagementStarter имя домена управления в качестве
параметра.
3. Запустить com,deiteI.adhhtpl.jiroJ>ynamieService^Iient.PoliclesStarter с
использованием файла политики policy-all. Нужно также передать
PoliciesStarter имя домеаа управления в качестве параметра.
4. Запустить com. dei t e[.adhh tpl.jiro.Dynamics ervicexlieat,Printer Client GUI
с использованием файла политики policy.all. Нужно также передать Prin-
terCIientGUI имя домена управления в качестве параметра.
На рис. б.30 приведено окно утилиты Igniter после того, как в лотке принтера
закончилась бумага.
На рис. 6.31 отображены действия динамического сервиса OutofPaperPoIicy
после получения уведомления об отсутствии бумаги.
На рис. 6.32 изображено окно утилиты viewlog после запуска политик
управления.
На рис. 6.33. изображено диалоговое окно, появляющееся в ответ на двойной
щелчок на записи в окне утилиты viewlog.
Jiro
301
Рис. 6.30. Отображение в окне утилиты Igniter события отсутствия бумаги в лотке
принтера
Рис. 6.31. Обработка события отсутствия бумаги в принтере динамическим сервисом
OutofPaperPolky
6.9. Заключительные замечания по поводу системы
управления принтером
Система, созданная в данной главе, имеет достаточно сложную логику. Мы ее
еще pas проследим, чтобы продемонстрировать всю элегантность предлагаемых
Jiro решений для сетевого управления.
В нашу систему входит несколько важных компонентов. Компонент Printer-
Management представляет собой точку входа в систему. PrinterManagementlmpl
реализует основную функциональность PrinterManagement. PrinterManageinent-
Impl делегирует вызовы к модели принтера (Printer) и получает события от
Printer. PrinterManagementlmpI также ответствен за запись информации обо всех
событиях, получаемых им от принтера.
Рис. 6.32. Журнал системы п
управлений событиями
е обработки динамическими сервисами политик
Рис. 633. Подробная информация о записи в журнале
Нашим управляемым объектом является модель принтера Printer. Printer
возбуждает три события и передает их сервису событий с темами: ".Printer.Error.Out-
OfPaper", ".Printer .Error. LowToner" или ".Printer .Error. Paper Jam". Сервис
событий передает события всем слушателям-наблюдателям (PrinterCUentGUI
и PrinterManagementlmpI) и первому ответственному слушателю в списке
ответственных слушателей сервиса событий (OntofPaperPolicylmpl или LowToner-
Policylmpl в зависимости от темы события).
Классы OutofPaperPolicylmpl и LowTonerPoHcylmpI являются реализациями
политик управления, которые мы развернули для автоматвзации решения
тривиальных и не очень тривиальных задач. OutofPaperPolicylmpl регистрирует
экземпляр PrinterEv cut Listener (слушателя, разработанного нами для всех связанных
с принтером событий) в качестве ответственного слушателя для обработки событий
с темой ".Printer.Error.OutOfPaper". Сервис событий посылает все события OutOf-
PaperEvent экземпляру PrinterEvent Listener, который делегирует события
OutofPaperPolicylmpl. Класс OutofPaperPolicylmpl обрабатывает событие OutOfPaper-
Event, вызывал метод addPaper классе Ргш ter Manage men tlmpl, затем делает
запись в журнал через сервис регистрации о предпринятом действии. LowTonerPolicy
обрабатывает события, связанные с низким уровнем тонера. LowTonerPolicyImpI
реагирует на события аналогично OutofPaperPolicylmpl.
Мы также создали консоль управления. Все события посылаются консоли
PrinterClientGUI, так как она записывает информацию обо все событиях по теме
".Printer.Error". PrinterClientGUI также отображает информацию о полученных
событиях. Она позволяет пользователям получать информацию о состоянии
принтера в любой момент времени и снимать задания на печать, находящиеся в очереди.
Это может быть полезным, например, при замятии бумаги принтером. Для снятия
заданий на печать PrinterClientGUI удаленно вызывает метод cancelPending-
Print Jobs класса PrinterMaHageinentlmpl через совместно используемую станцию.
В результате была разработана автоматизированная модульная система
управления, которая ведет журнал событий и определенным образом реагирует на
события. Это показано на рис. 6.34.
Jiro в данной системе сетевого управления позволяет делегировать
ответственность элегантно и эффективно. Jiro не только связывает консоль управления
администратора системы с устройствами, но и позволяет создавать расширяемые
динамичные средства управления сетями.
6.10. Ресурсы в Internet и во Всемирной паутине
jiro. com/ docwantc«at*r/
Данный сайт содержит документы, связанные с технологией Jiro.
www. jira. соя/averviev/
На этом сайте можно найти обзор технологии Jiro.
»unjweb-001.jiro.com/faqs
Сайт содержит часто задаваемые вопросы по техвологии Jiro.
www.jiro.cam/•ducat±om/c«cipes/
На данном сайте имеются ссылки на информацию о сервисе событий.
www. ас*. carlcton. c»/n«tjn*nage/NMf огЭ0в/31ир1вМ(. html
В статье обсуждаются проблемы сетевого управления в 90х годах прошлого века.
sunjweb-001. jiro.com/c()i-bin/DltiHi*te.cgi?action=»inti:o
Это форум, посвященный технологии Jiro
Резюме
• Jiro предоставляет инфраструктуру для разработки систем управления распределенными
ресурсами в гетерогенных сетях.
• Технология Jiro упрощает и автоматизирует управление распра деленным и ресурсами.
• Автоматизация слежения за состоянием сети и управления сетью позволяет свести к
минимуму вмешательство администратора в стандартных ситуациях.
• Jiro позволяет создавать системы управления, которые легко развертывать и расширять.
Приложение управления
Рис. 6.34. Диаграмма потоков информации г системе управления принтером
• Texi
Jiro использует трехуровневую архитектуру для построения с:
а управле-
SNMP и С1М.
ресурсы и сервисы управ лена)
• Jiro поддерживает индустриавьные стандарты, i
■ Домен управления Jiro содержит управляемые с
которые управляют ресурсами.
• При запуске Jiro необходимо указать имя домеиа управления.
• При запуске Jiro запускаются сервер классов, rmid, сервис транзакций, совместно
используемая станция Jiro, сервис управления, сервис регистрации, сервис событий и
сервис планирования.
• В состав Jiro входит сервис обнаружения для каждого домена управления, так что
статические и динамические сервисы регистрируются сервиозм обнаружеиня.
х сервисов входят сервис управления, сервис
регистрации, сервис событий и сервис планирования, сервис транзакций. Статические сервисы
доступны всему домену управления.
• Класс javax.fma.servicee. Service Finder дает удобную возможность определить
местоположение логических сервисов.
• Публикатор событий посылает события сервису событий. Подписчик на события
получает события от сервиса событий.
• Клиент или сервис могут либо послать сообщение сервису регистрации, либо запросить
у сервиса регистрации сообщения, удовлетворяющие опраделенному поисковому крите-
• Если приложение использует сервис регистрации, то требуется файл ресурса с описанием
ключа и значения (шаблона) сообщения.
• Сервис планирования включает в сабя методы для планирования выполнения задач в
определенные даты и моменты времени, чераз интервалы времени либо периодически,
например, раз в сутки.
• Метод newRepeatedDateSchednle создает план для периодического выполнен ал задач.
• Имеются три политики выполнения задач: Scheduling Service. NONE, SchednlingServi-
ce.ONE и SchedulingService.ALL.
• Статические сервисы всегда присутствуют в домене управления, а динамические сервисы
не всегда.
• В Jiro входит станция управления, которая поддерживает взаимодействие динамических
сервисов с управляемыми ресурсами.
• Динамические сервисы, обеспечивающие доступ к управляемым ресурсам, называются
фасадом управления.
• Чтобы сделать динамический сервис доступным в домене управления, необходимо
реализовать динамический сервис и развернуть его на станции в домене управления.
• Реавизация метода getLooknpEntrles делает класс динамическим сервисом.
• Класс, в котором определен метод getLooknpEntries, называется точкой входа. Такой
класс является точкой входа в динамический сервис.
• Б Jiro имеются утилиты jiroc и jirocw для создания заглушек сервисов.
• Перед инициавизацией динамический сервис должен быть развернут на станции. Для
рнзнертывания динамических сервисов в состав Jiro включена утилита jar deploy.
■ В состав Jjro входит утилита jarpack с графическим пользовательским интерфейсом
и утилита командной строки jarpackw для создания файлов, используемых jardeploy.
• Для использования утилиты jarpackw требуются три JAR-файла: интерфейсный JAR-
фебл, JAR-файл реализации и загрузочный JAR-фанл.
• Интерфейсный JAR-файл содержит все интерфейсы, которые реализуют динамические
сервисы, а также классы, от которых зависят эти интерфейсы.
• JAR-файл реализации содержит все классы, которые требуются для работы
динамических сервисов.
• Загрузочный JAR-фаЙл содержит все классы, которые будут доступны клиентам
динамических сервисов для загрузки через сеть.
Терминология
automated management — автоматизирован- Federated Management Architecture —
ное управление интегрированная архитектура управления
Ъаве services — основные сервисы getControllerService, метод ServiceFinder
cancel, метод класса Ticket getEventService, метод ServiceFinder
centralized management — нейтрализован- getLogService, метод ServiceFinder
нее управление getLooknpEntrle*, метод ServiceFinder
ClientControiler, класс getSchednllngService, метод ServiceFinder
Common Information Model {CIM) — общая get Transact ion Service, метод ServiceFinder
информационная модель heterogeneous network — гетерогенная сеть
Context, класс Igniter, утилита
Controller, класс IllegalArgnment Except ion, исключение
distributed resources — распределенные ре- implementation JAR — JAR-файл реализа-
сурсы ции
download JAR — загрузочный JAR-файл interface JAR — интерфейсный JAR -файл
interoperability — способность к взаимодей- newDateScnedule, метод SchednllingService
ствию newDnrationSchednle, метод
jardeploy, утилита ScbednllingService
jarpack, утилита newRepettedSchedule, метод
jarpackw, утннята SchedollingService
Jiro Technology Software Development Kit — post, метод EventService
набор инструментальных средств разра- search, метод класса LogSearchCriteria
боткн для технологии Jiro ServiceFinder, клаос
jiroc, утилита Simple Network Management Protocol
jirecw, утилита (SNMP) — простой протокол сетевого
Localizable Message управлении
log, метод класса LogSeerchCrlteria static management services — статические
LogMeesage .AUDIT сервисы управления
LogMeesage.DEBUG station — станция
LogMeesage.ERROR StationAddress, класс
LogMessage.INFO snbecribeObserver, метод EventService
LogMessage.TRACB thres-tier architecture — трехуровневая ap-
LogMessage. WARNING хитектура
management facade — фасад управления Throwable
newClientController, метод TransactlonManager
Controller Service viewlog, утилита
Упражнения для самоконтроля
6.1. Заполните пропуска в оледующих предложениях:
a) Jiro является реализацией .
b) Jiro предоставляет пять статических сералсоя: . , ,
с) Класс _
и (LogService).
« динамического сервиса должен быть опреДаиен ы
{) Для использования утилиты jarpackw необходимо наличие трех JAR-файл о i
g) Утилита Jiro используется для развертывания динамических сервисов.
6.2. Ответьте, является ли каждое из следующих высказываний истинным или ложным.
Если высказывание ложно, объясните, почему.
a) Jiro позволяет создавать не зависящие от платформы системы управления сетями.
о) Приложения управления, которым необходимо прослушивать события от сервиса
событий, должны зарегистрировать RemoteEventListenerlmpl и использовать Web-сервис.
c) Устройства, использующие Simple Network Management Protocol, могут
управляться с помощью Jiro.
d) Jiro может самостоятельно управлять сетевыми устройствами.
e) Сервис событий Jiro передает события всем ответственным слушателям,
зарегистрированным для получения данных типов событий.
Ответы на упражнения для самоконтроля
6.1. a) Federal Manageioent Architecture, b) сервис управления, сервис событий, сервис
регистрации, сервис планирования, сервис транзакций, с) ServlceFind. d) ресурса, е) get-
LooknpEntries, f) интерфейсный JAR-фанл, JAR-файл реализации, загрузочный
JAR-файл, g} jardeploy.
6.2. а) Истинно, b) Ложно. Если управляющее приложение регистрирует
RemoteEventListenerlmpl в сервисе событий, то использование Web-сервера не нужно, так как
управляющее приложение и ставция Jiro совместно используют слушатели
RemoteEventListenerlmpl. Если упрааляющее првложевие использует собственную
реализацию RemoteEventListenerlmpl, то необходим Web-сервер, чтобы сервис событий мог
Jiro
307
динамически загрузить реализацию Remo t eE vent List en erlntpl. с) Истинно, d) Ложно.
Управление устройствами осуществляется через сервисы, е) Ложно. Сервис событий
передает соответствующие события первому ответственному слушателю в цепи
ответственных слушателей. Если ответственный слушагель возбуждает Е vent Not Handle-
Exception, то сервис событий передает событие следующему ответственному
слушателю в цепи. Распространение событий будет продолжаться по ответственным
слушателям в цепи, пока какой-либо из ответственных слушателей не возбудит исключение
EventNotHandleException.
Упражнения
6.3. Опишите, в чем статические и динамические сервисы схожи, а в чем различны.
6.4. Опишите различия между ответственными слушателями и слушателями-наблюдателями.
6.5. Сравните динамический сервис PrinterMaaagement с сервисами политик управления
OntofPaperPoHcy и LowTonerPolicy. В чем оаи схожи? В чем различны?
6.6. Модифицируйте модель принтера, включив метод bandlePaperJam, позволяющий
устранять замятие бумаги принтером. Внесите изменения в динамический сервис Printer-
Management, чтобы сделать доступной новую операцию. Наконец, внесите изменения
в PrinterClientGUI, включив кнопку для вызова дайной операции через динамический
сервис.
6.7. Создайте и разверните следующую политику управления:
"Принтер никогда не перестанет печатать иа-s* замятия бумаги."
6.8. В строках 52-54 PrmterManagementlmpl.java {рис. 6.5) создается программный поток
для моделирования поведения принтера. В реальных ситуациях динамический сервис
установит удаленное соединение с принтером. Это соединения может быть выполнено
с помощью любого протокола сетевого управления (SNMP, WEBM, RMI). В данном
упражнении:
a) Определите интерфейс RemotePrinter, который делает доступными операции
класса Printer (рис. 6.8). RemotePrinter должен расширять интерфейс Remote. Создайте
приложение RemotePrinterlntpl, которое запускает программный поток Printer. Класс
RemotePrinterlinpi должен раавиэовывать интерфейс RemotePrinter. ReinotePrinter-
1шр] должен передавать RMI-заглушку самого себя сервису обнаружения Jini в домене
Jiro. Класс RemotePrinterlmpl должен делегировать вызов удаленных методов
объекту Printer.
b) Внесите изменения в PrinterManagementlmpl (рис. 6.5) для получения заглушки
RemotePrinter от сервиеа обнаружения Jini в локальном домене Jiro. Динамический
сервис PrinterManagement должен делегировать операции со управлению принтером
заглушке RemotePrinter.
Литература
«Declaring a Class as Dynamic Service.» Jiro Web site
< www. j iro. со ш/educat ion/recipes/ eervice/>.
Deri, L. «Network Management for the 90s», Papar, IBM Zurich Research Laboratory,
University of Berne. <www.sce.carleton.ea/netmanage/NMfor90s/SimpleNM.html>.
• Executive Overview.» Jiro Web site <www.jiro.coin/overview/>.
«Frequently Aaked Questions.» Jiro Web site <www.jiro.com/faqs/>.
«Jiro Document Center.» Jiro Web Site <www.jlro.com/documentcenter/>.
• Jiro Recipes.» Jiro Web site <www.jiro.cem/edncation/recip«s/>.
«Jiro Technical Overview. • Jiro Website <www.jiro.cein/overview/tech_overview.htinj>.
«Jiro Technology Discussion Forum.» Jiro Web site
< www,] i ro. сош/cgi -bin/ Ultimate .cgi ?action=int ro>.
Monday, P.. and W. Connor. The Jiro Technology Programmer's Guide And Federated
Management Architecture. Boston, MA: Addison-Wesley, 2001.
CORBA. Часть 1
Цели
• Познакомиться с CORBA
(Common Object Request
Broker Architecture).
« Познакомиться с Interface
Definition Language (TOL).
• Научиться использовать
CORBA при разработке
приложений.
• Понять, что такое
распределенные исключения.
• Реализовать приложение
Deltel Messenger
с использованием CORBA.
• Сравнить CORBA с другими
технологиями построения
распределенных систем.
Б каждом хозяине есть что-то
от слуги, в каждом слуге —
что то от. хозяина.
Марк Туллий Цицерон
Непревзойденное мастерство —
делать обычные вещи
необычным образом.
Бук ер Т. Вашингтон
Достоинство, по большей
части, — это соблюдение правил.
Ральф Уолдо Эмерсон
Часто поражаешься, как простое
изложение на бумаге какой-нибудь
ситуации помогает увидеть пусть
не выход из нее. но путь к ее пониманию.
Бенсон А. К.
310 Глава 7
7.1. Введение1
Необходимо помнить, что разработанные до появления Java программные
системы все еще функционируют, нужно и далее работать с нимн, более того
извлекать пользу из их существования. К тому же взрывной рост Всемирной паутины
сделал критически важной разработку систем, которые были бы распределенными
изначально, чтобы не воспроизводить постоянно одну и ту же структуру
взаимодействия. Java дает возможности, сделавшие это осуществимым, но в ней не было
высокоуровневых классов, инструментальных средств и абстракций для
построения больших распределенных систем. В Java отсутствовала возможность
осуществлять локальные вызовы, которые, на самом деле, были бы вызовами удаленных
объектов. Для Java не существовало подходящей инфраструктуры, и
производители программного обеспечения для распределенных объектов стали развивать
инфраструктуры своих продуктов для поддержки Java. Инфраструктурой, которая
лучше всего подходила для Java, оказалась CORBA.
1 Эта глава написана с участием Карлоса Валкарсела (Carlos Valcarcel) из компании
EinTech, Inc.
CORBA. Часть 1 311
CORBA означает Common Object Request Broker Architecture (общая
архитектура брокера объектных запросов). Точнее, CORBA — это своего рода «клей»,
интегрирующая технология. Она позволяет программам, написанных на разных
языках, работающих в различных узлах сети, взаимодействовать друг с другом так же
просто, как если бы они находились в адресном пространстве одного процесса. На
концептуальном уровне CORRA описывает архитектуру взаимодействующих
сервисов.
® Общая методическая рекомендация 7.1
Задача CORBA не в том, чтобы внедрять в жизнь удачный опыт
системной интеграции, а всего лишь в том. чтобы сделать возможной саму
интеграцию изолированных систем.
Для решения этой задачи CORBA использует объектную технологию.
Инкапсуляция, наследование, полиморфизм и динамическое связывание скрывают детали
реализации, делая работу CORBA прозрачной. Прозрачность — основная цель
CORBA. Прозрачность позволяет разработчикам и системным интеграторам
определять стандартные сервисы, предоставляемые существующими системами, и
обеспечивать доступность этих сервисов для других систем, которые в них нуждаются.
В пределах такой прозрачной инфраструктуры множество сервисов доступно всем
пользователям, которые в них нуждаются. Клиенты, использующие CORBA,
получают преимущества в трех областях: прозрачность вызова (invocation transparency),
прозрачность реализации (implementation transparency) и прозрачность
локализации (location transparency).
Прозрачность вызоза определяет точку зрения клиента, посылающего
сообщение серверу. Язык, используемый при реализации клиента, определяет, каким
образом вызываемый объект получает сообщение, как параметры передаются в стек,
как осуществляется обращение к методам данного объекта, как передаются
возвращаемые значения. Если сервер (объект, ответственный за предоставление
набора услуг) задан в соответствии с правилами CORBA, то клиент может вызывать
методы этого совместимого с CORBA объекта так, как и любого другого объекта,
реализованного в используемом языке программирования. Этот образ действий
естественнее воспринимается разработчиками, использующими
объектно-ориентированные языки программирования (например, Java, C++ и Smalltalk), но он
также справедлив и для разработчиков, использующих языки, не являющиеся
объектно-ориентированными (например, С, COBOL или Lisp)- CORBA
поддерживает целый ряд языков программирования (всего их 10), включая С, C++, Java,
COBOL, Ada, Lisp, Smalltalk, PL/1, Python и IDLscript.
Прозрачность реализации — это инкапсуляция, примененная к
распределенным системам. Клиенту о вызове метода известны три вещи: имя метода,
параметры метода (если они есть) и тип возвращаемого методом значения. Клиента не
касается, как внутренний код метода обребатывает параметры и обеспечивает
возврат правильных значений» — клиент может прадоставнть серверу информацию
относительно любых своих ограничений до того, как вызывать методы. Клиент
вызывает метод средствами языка, используемого при разработке клиента, а код,
вызываемый для выполнения метода, реализуется на любом языке, который
поддерживают отображения ОМС
Прозрачность локализации позволяет клиенту вызывать CORBA-совместимый
код, который может выполняться в любом узле сети. Прозрачность локализации
основана на свойстве прозрачности реализации — клиенты вроде бы делают
локальные вызовы, но, на самом деле, они вызывают код, написанный на разных
языках и выполняющийся за пределами адресного пространства процесса клиента
(например, сервлет Java, работающий на компьютере в Нью-Йорке, обменивается
данными с унаследованным приложением для работы с базами данных, реалиао-
312
Глава 7
ванным на языке программирования COBOL, это приложение может работать на
компьютере в Японии). Все это возможно благодаря концепциям объектной
технологии (инкапсуляции, наследованию и полиморфизму), которые используются
независимым от реализации образом.
Ответственным за CORBA является консорциум Object Management Group
(OMG). Основанный в апреле 1989 г. OMG преследует далеко идущие цели и имеет
в своем составе около 800 членов. Его основная цель — это создание «рынка
программного обеспечения, основанного на использовании компонентов, путем
ускорения процесса внедрения стандартного объектного программного обеспечения»1.
Большим шагом на пути к созданию рынка программных объектов было создание
консорциумом OMG архитектуры Object Management Architecture (ОМА).
Архитектура Object Management Architecture является отличительным
признаком CORBA по сравнению с другими технологиями распределенных систем.
Объекты в объектно-ориентированной системе взаимодействуют в процессе решения
прикладных задач. ОМА (в качестве эталонной архитектуры) описывает систему,
состоящую из взаимодействующих сервисов, решающих прикладную задачу. Уровней
модульности у такого решения больше, но концепция объектов (или компонентов),
совместно работающих для достижения общей цели, остается той же самой.
Каким образом с помощью эталонной архитектуры достигаются цели
системной интеграции, прозрачность, простота применения так, что это позволяет
последовательно и просто урегулировать проблемы, связанные с использованием
различных языков программирования, платформ, распределенности? Ответ на этот
вопрос заключается в следующих трех акронимах: IDL, ORB и ПОР.
Чтобы объекты могли взаимодействовать так, как если бы «общались» на
одном языке, они должны использовать общее для языков, на которых они
реализованы, внешнее представление. Чтобы программы, написанные на таких языках
программирования как COBOL, Java, С и Python могли взаимодействовать друг
с другом, их должен объединять промежуточный язык. Этот промежуточный язык
должен быть простым в использовании, легким в изучении и обусловливать
минимальные накладные расходы.
Язык OMG Interface Definition Language (OMG IDL™), обычно просто IDL,
позволяет разработчикам описывать интерфейсы для тех типов данных, которые
используются удаленно, независимо от языка реализации. IDL — это чисто
описательный язык, IDL-файлы не включают никаких деталей реализации (за
исключением тех случаев, когда специально включаются комментарии, описывающие
рекомендации по реализации). Используя синтаксис в стиле C++, разработчики
описывают только две вещи: интерфейс объекта или структуры данных, которые
методы объекта могут принимать и возвращать в качестве значений при вызове.
IDL-компилятор принимает IDL-файл (этот файл должен иметь расширение .idl)
и генерирует код, необходимый клиенту для вызова CORBA-совместимого объекта
и CORBA-совместимому объекту, чтобы принимать и возвращать значения. Когда
мы говорим CORBA-совместимый «объект», мы не делаем никаких
предположений относительно сущности этого «объекта»: является этот «объект» объектом
Java или процедурой языка программирования COBOL, которая реализует
операции, определенные в IDL-файле, способом, который не является
объектно-ориентированным. Код, который реализует операции (т.е. обрабатывает запросы
клиента к CORBA-объекту), называется сервантом (servant). В языке, не являющемся
объект но-ориентированным, каждая операция реализуется отдельно. В
объектно-ориентированном языке в одном серванте могут быть реализованы все
необходимые операции. Клиенту ничего неизвестно об особенностях реализации, поэтому
сервант может существовать только во время вызова метода до тех пор, пока не
прекратит работу сервер, на котором выполняется данный сервант, или какое-то
OMG Background Information. www.omg.org/Dews/abont, Copyright 1997-2000.
CORBA. Часть 1
313
промежуточное время (время существования объекта контролируется объектным
адаптером, через который проходят есе обращения к сервантам. Мы будем
говорить об этом более подробно в разделе 7,4).
ШЬ-компилятор создает для каждого компилируемого ШЬ-файла целый ряд
файлов, ориентированных на конкретные языки программирования. Проекту
потребуется столько ШЬ-компиляторов, сколько языков программирования
используется при разработке снеге мы. Для создания на Java интерфейса к
унаследованной COBOL-системе потребуется два ШЬ-компилятора: один для создания
клиентской программы на языке Java, второй для создания серверной программы на
языке COBOL. Например, внешнему интерфейсу на языке Java потребуются
следующие файлы, сгенерированные на языке Java, для взаимодействия с серверной
системой, которая называется StockTicker:
StockTicker.Java
StockTickerOperations. java
StockTickerHolder.j ava
StockTickerHelper.Java
_StockTickerStub.java
StockTicker.java содержит основное определение удаленного сервера.
Исходный файл StockTickerOperatioas содержит определения методов операций,
поддерживаемых СОНВА-объектом. Класс _StockTickerStub отвечает за
взаимодействие на стороне клиента, а файл StockerTickerHelper определяет класс,
используемый клиентом для выполнения задач, связанных с CORBA.
Эти файлы скрывают механизм реализации вызова от вызывающей
программы. StockTicker.java и StосkTickerOperations,java определяют интерфейсы для
использования классами, связанными с реализацией; _StockTickerStub.java
определяет код CORBA-вызова для осуществления вызовов удаленных методов на
сервере. Файлы StockTickerHelper.java и StockTicker Holder, java —
вспомогательные служебные файлы, назначение которых мы обсудим позже.
Поставщик серверного компонента предоставляет IDL-фанл, описывающий API
своего серверного объекта. Используя этот файл и ШЬ-компилятор, разработчик
может создать клиентские файлы, необходимые для непосредственного обращения
к этому серверному объекту.
Если бы в написанном на С графическом пользовательском интерфейсе системы
StockTicker потребовалось бы посылать сообщения серверным объектам,
написанным на Java (CORBA-совместимые Java-объекты), с помощью ШЬ-компилятора
потребовалось бы создать следующие Java-файлы, чтобы серверные Java-объекты
могли получать сообщения от любых вызывающих программ:
StockTicker.java
StockTickerOperation*.java
_StockTickerIinplBase. Java
Файлы StockTicker.java и StockTickerOperations.java те же, что и раньше
(интерфейсы для использования классами, связанными с реализацией), a _Stock-
TickerlmplBase.java представляет собой CORBA-код, используемый сервером для
приема вызовов методов и передачи возвращаемых значений через сеть.
Дополнительно к объявлению Java-интерфейсов, используемых
разработчиками, и клиентские, и серверные файлы, определенные выше, реализуют базовые
классы посредников (proxies) — «заместителей» других объектов. Посредники
создают для клиента видимость того, что он посылает сообщение одному объекту, в то
время как, на самом деле, этот клиент посылает сообщение другому объекту.
CORBA определяет два взаимосвязанных посредников: заглушку (stub) и скелет
(skeleton). Заглушка является посредником на стороне клиента, скелет — на стороне
сервера. Оба посредника скрывают использование ORB и от клиента, и от сервера.
314
Глава 7
Object Request Broker — это акроним CORBA, связанный с брокером объектных
запросов. Основным назначением ORB является функциональная совместимость.
Все CORBA-совместимые объекты должны использовать какой-либо брокер
объектных запросов для передачи или приема запросов методов, но сами объекты
редко имеют дело непосредственно с брокером объектных запросов. В следующем
далее примере обратите внимание на сходство кода для соединения различных
частей. Сходство кода и есть OMG в действии. Способность к взаимодействию,
заложенная в CORBA, остается одной и той же независимо от конкретной
реализации CORBA.
Ш Совет по переносимости программ 7.1
За исключением тех случаев, когда используются дополнительные, зови
сящие от поставщика возможности CORBA 3.0, замена уже
установленного брокера объектных запросов (ORB) на новую версию не должна
требовать модификации существующего кода CORBA.
Когда клиент запрашивает операцию, реализуемую распределенным объектом,
брокер объектных запросов данного клиента для выполнения вызова использует
объектную ссылку. Например, если клиент был написан на Java, то он поступает
так, как если бы указанная ссылка была ссылкой на локальную версию
вызываемого объекта. Объектная ссылка, используемая клиентом (поддерживаемая
открытым API вызываемого объекта, описанным в ШЬ-файле), содержит
непрозрачную сетевую ссылку. (Непрозрачную — так как ни клиент, ни сервер не могут
извлечь из этой ссылки информацию о деталях реализации.) Кннент запрашивает
операцию, реализуемую распределенным объектом посредством объектной
ссылки, поскольку Interoperable Object Reference (-ГОД) ссылается именно на объектную
ссылку, структура которой хорошо известна брокерам ORB при условии
использования OMG-совместимых протоколов (например, ПОР, о котором скоро пойдет
речь). Источником объектной ссылки является объектный адаптер,
указывающий на удаленный объект. Объектная ссылка содержит три важных фрагмента
информации: местоположение распределенного объекта (адрес, но не адрес в
памяти); ссылку на адаптер, создавший объектную ссылку, и идентификатор объекта
серванта. Все CORBA-совместимые объектные брокеры «знают», что такое
объектные ссылки (для полной функциональной совместимости они должны также
«понимать* ссылки IOR) и как их использовать для установления соединений
клиентов с сервантами. Последнее, что необходимо для обеспечения функциональной
совместимости, — это соадание и синтаксический анализ IOR в протоколе для
отправки запроса через сеть.1 Однако знания IOR недостаточно, чтобы брокер
объектных запросов одного производителя мог уверенно взаимодействовать с
объектным брокером другого производителя. Чтобы сделать возможным надежное и
уверенное взаимодействие, консорциум OMG разработал спецификацию протокола
Internet Inter-ORB Protocol (HOP).
Все производители должны обеспечивать поддержку ПОР, чтобы их объектные
брокеры были CORBA-совместимыми. Производители могут также поддерживать
свои собственные протоколы, но, как минимум, они должны поддерживать ПОР.
ПОР — это протокол обмена сообщениями между объектными брокерами.
Рядовому разработчику не требуется ничего анать об ПОР; Администратор сети должен
поддерживать соответствующий протокол брокера объектных запросов.
Объектные брокеры, которые предполагают обмениваться между собой сообщениями,
должны «говорить» на языке одного и того же протокола. В том случае, если
объектные брокеры поддерживают разные протоколы, должны быть разработаны спе-
1 The Common Object Request Broker Architecture and Specification, p. 5-3. Editorial
Revision: CORBA 2.4.2: February 2001. (Спецификация и архитектура CORBA)
CORBA. Часть 1 315
циальные адаптеры для преобразования протоколов, чтобы обеспечить обмен
сообщениями между объектными брокерами, поддерживающими нестандартные
протоколы. При установке брокера объектных запросов ПОР должен устанавливаться
по умолчанию в качестве стандартного протокола, хотя ПОР может яаляться
неединственным протоколом.
ПОР является реализацией другого стандарта OMG — General InterORB
Protocol (GIOP). GIOP определяет сообщения, необходимые объектным брокерам для
обмена данными между собой, и обеспечивает поддержку лежащего в основе этого
взаимодействия транспорта для платформы, на которой функционирует данный
брокер объектных запросов. TCP/IP является предпочтительным транспортным
средством — спецификация включает также поддержку Novell IPX и OSI.
Производители могут обеспечивать поддержку и других транспортных механизмов.
Java, с ее встроенной сетевой поддержкой, и CORBA, с ее механизмами
обеспечения соединений распределенных объектов, яаляются взаимодополняющими
технологиями.
Для каждого языка программирования, поддерживаемого OMG, должно
существовать преобразование из IDL на этот язык. Вскоре после появления Java, целый
ряд производителей объектных брокеров разработали свои средства
преобразования, чтобы удовлетворить потребность в распределенных Java-при л ожеииях.
Консорциум OMG принял IDL-Java в качестве стандарта CORBA в июле 1998 г. После
того как OMG официально утвердил IDL-Java, производители выпустили новые
версии своих продуктов, соответствующие официальному стандарту. Соответствие
IDL-преобразований стандарту — это важный шаг на пути обеспечения
целостности в мире CORBA. Отображение IDL-Java определяет, как следует
преобразовывать ключевые слова и типы данных IDL в конструкции Java. Java 2 был первой
версией, выпущенной корпорацией Sun, непосредственно поддерживающей OMG-
отображение. Мы будем говорить об IDL-Java в ближайшее время.
7.2. Последовательность действий
Для реализации распределенной системы с использованием Java и CORBA
необходимы следующие шаги:
1. Выполнить анализ и проектирование на основе моделирования предметной
области, моделирования системы и декомпозиции системы на ряд подсистем.
2. Создать ID L-описание путем спецификации API рас предал енных
подсистем, и спецификации структур данных, которые передаются через границы
системы.
3. Реализовать сервант, используя файлы, сгенерированные IDL-компилитором.
4. Реализовать клиент, используя файлы заглушек, сгенерированные IDL-
комп илято ром.
5. Принять решение о методе распространення объектных ссылок сервантов
(обычно это делается с помощью сервиса именования, но не обязательно).
6. Запустить реализацию серванта.
7. Запустить клиент,
Ш Общая методическая рекомендация 7.2
Реализация распределенной системы — это нетривиальная задача. При
использовании CORBA в качестве инфраструктуры, позволяющей
объединить разного рода унаследованные системы, разработчики должны следо
вать сложившейся практике разработки программного обеспечения.
316 Глава 7
шг&\ Хороший стиль программирования 7.1
IgjjSI Существует несколько способов и методик моделирования предметных
областей. Убедитесь, что выбранная вами модель соответствует
решаемой задаче (иначе может случиться так, что вам придется
переделывать проект).
Архитектура ОМА консорциума OMG остается предпочтительной эталонной
моделью для решения вопросов, связанных с архитектурой. Она определяет
архитектуру в виде совокупности взаимодействующих сервисов. Рассматривайте
объявляемые вами подсистемы кок разделение ответственности. Появляется больше
возможностей для многократного использования процессов, если ваша система
реализует фундаментальные сервисы, которые могут быть востребованы другими
группами разработчиков.
После определения подсистем нужно решить, каким из них придать форму
распределенных сервисов. Если предположить, что структура всех подсистем
реализует функциональность распределенных совместно используемых сервисов, то
перевод таких подсистем на платформу CORBA существенно упрощается. В любом
случае, открытый API такого сервиса будет доступен клиентам. Для
объектно-ориентированного подхода обычно интерфейс, описываемый в IDL, может являться
некоторым подмножеством всех реализуемых сервером функций. И даже после
реализации сервера (мы используем Java в качестве языка программирования),
разработчиков продолжает волновать вопрос о том, сколько дополнительных
функций (административных, связанных с защитой и т.д.) предоставить серверу.
Если существуют структуры данных, которые сервер должен возвращать
клиенту (или принимать от него), то описание и определение этих структур данных
должно присутствовать в IDL-файле. Частично это является возвратом к тому
способу определения структур данных, который используется в С, но все же модель
CORBA предоставляет возможность процедурным языкам программирования
описывать конструкции в «псевдо-объектном» стиле. Две конструкции CORBA
поддерживают этот механизм: struct и value type. Клиент и сервер передают IDL-
структуры (struct) туда и обратно в качестве обычных данных, чтобы предоставить
возможность локальной обработки информации. Стандартная оптимизация
включает в себя отправку этих данных непосредственно клиенту, чтобы освободить
сервер от избыточных запросов. Конструкция valnetype является IDL-описанием
некоторой сущности, включающей и данные, и методы. Конструкция struct состоит
только из данных (конструкция struct, скомпилированная в Java-код, определяет
открытые атрибуты), тогда как value type инкапсулирует данные, и добавляет
логику, необходимую для создания объекта, простого или сложного, какой
требуется. В разделе 7.8.5 говорится о valuetype более подробно.
I—_-. Совет по повышению эффективности 7.1
Г?^| Создавайте все методы ориентированными на транзакции. Помните,
каждый вызов метода — это сетевой вызов. Модульность сервисов должна
быть такой, чтобы нужный уровень функциональности достигался как
можно меньшим числом методов (желательно вызовом одного метода).
Если требуется извлекать адресную информацию о клиенте: номер дома,
улицу, город, штат, почтовый индекс — создайте класс Address, в кото
ром будет инкапсулирована эта информация. У вызывающей программы
будет возможность осуществить одно обращение к Address вместо того.
чтобы выполнять пять отдельных вызовов.
После объявления API сервера в виде ШЬ-файла единственноз, чего не достает —
это собственно реализации сервера. Компилятор TDL анализирует IDL-файл и
генерирует совокупность вспомогательных файлов, необходимых серверу для обработки
CORBA. Часть 1
317
входящих запросов. После того как созданы вспомогательные файлы, разработчик
может писать код реализации; до этого момента единственное, что было сделано
разработчиком — это объявлены интерфейс, атрибуты и данные, необходимые серверу
для обработки запросов. Сначала разработчик может реализовать фиктивный
сервер, который просто возвращает тестовые данные, чтобы другой разработчик мог
написать клиент (или, возможно, тестовый клиент) для тестирования обращений
к серверу. Более типичный пример — зто написать сервер, который является
оболочкой унаследованного приложения, но скрызает это от клиентов.
Допустим, что объявленноз API сервера находится в ШЬ-файле и
IDL-компилятор скомпилировал этот файл. Теперь клиент может использовать
сгенерированные для клиента файлы, необходимые ему для взаимодействия с сервером.
Клиенту нужны только файлы, сгенерированные IDL-компилятором, чтобы обеспечить
прозрачное сетевое взаимодействие. Можно создать локальную версию сервера
(вместо того, чтобы использовать объектную ссылку CORBA, можно просто
выполнить операцию new ServerlmplO) и отправлять сообщения напрямую, но такой
сервер уже не будет распределенным объектом, он будет работать в адресном
пространстве клиента, утрачивал преимущества слабой связи между компонентами
системы, имевшимися первоначально, благодаря использованию интегрирующего
кода, размещенного в сгенерированных файлах.
7.3. Первый пример. System Clock
Данный пример предоставляет базовый сервис времени и дает клиенту
возможность запрашивать текущее время. Предъявляются следующие требования:
• Запрашивать текущее системное время.
• Отображать текущее системное время в окне графического
пользовательского интерфейса.
Интерфейс сервера System Clock включает один метод cnrrentTiineMillis
(который делает вызов метода класса java.lang.System). Этот метод возвращает
системное время в виде значения типа long. Для Java более подходящим был бы объект
Date. Если бы это был пример CORBA Time Service, еще более подходящим был бы
Universal Time Object. Однако значение типа long позволяет упростить этот
Java-пример (обойти нетривиальные вопросы, связанные с сервисом времени,
например, с задержкой при передаче сообщений в сети),
7.3.1. SystemClock.idl
На рис. 7.1 приведено ШЬ-описание сервера System С lock. Строки 1 и 2
представляют собой однострочные комментарии; следуя в этом стандартному
синтаксису C++, где двойной слэш (//) означает однострочный комментарий. Строка 4 —-
первая строка собственно «кода» (CORBA не является предписанием реализации,
поэтому IDL-описание формально программным кодом не является). Ключевое
слово module связывает данное имя непосредственно с пакетом Java. Вложенные
имена module, соединенные вместе, дают полное ими пакета. Имя module на
рнс. 7.1 — clock.
Фигурные скобки в строках 4 и 10 обозначают границы области действия
блока. Заметим, что в IDL точка с запятой всегда завершает блок (включая
объявление module). Java, С и C++ являются языками с блочной структурой и используют
похожий синтаксис, но в них блок не рассматривается как строка, требующая для
завершения точки с запятой. Отсутствие точки с запятой в IDL является
синтаксической ошибкой.
318 Глава 7
1 // Рис. 7.1. ayatemclock.idl
2 // IDL-описание SystemClock
3
4 module clock {
5
6 // Определение CORBA-совместимого сервиса
7 interface SystemClock {
8 long long currentTimeMillie();
9 };
10 );
Рис. 7.1. IDL-описание сервера SystemClock
Строки 7-9 являются объявлением интерфейса сервере SystemClock. Является
ли типом этого сервера SystemClock на самом двле? С позиции клиента ответ
утвердительный — указанный сервер принадлежит к типу данных SystemClock. С
позиции сервера ответ отрицательный — указанное объявление не является
непосредственно объявлением типа. Указанный сервер представляет собой производный класс
(конкретную реализацию) интерфейса SystemClock. Отсутствие привязки к
клиенту является сознательным, допуская произвольную реализацию сервера.
Фигурные скобки в строках 7 и 9 отмечают границу объявления interface. Еще
раз обратите внимание, что точка с запятой в конце строки 9 используется для
указааия на завершение блока.
Строка 8 — объявление метода/функции/сообщения. Все, что объявляется на
языке IDL является public, вдетому в ШЬ-интерфейс ах отсутствуют специальные
ключевые слова для public, private или protected (хотя у метатипов component
и value type есть ключевые слова private и public). Метод currentTimeMillis, по
определению, public и возвращает ШЬ-тип long long, соответствующий типу long
в Java (long в IDL соответствует типу int в Java).
Компиляция файла systemclock.idl компилятором Java-IDL idlj
осуществляется так:
idlj -td c:\src -pkgPrefii: clock con.deitel.advjhtpl.idl
-fall »yetemclock.idl
Вскоре мы рассмотрим утилиту Java idlj и ее параметры командной строки.
После компиляции файла systemclock.idl компилятор Java-IDL создает следующие
файлы на стороне сервера:
SystemClock.Java
SystemClockOperation*.Java
_SystemClockImplBase.Java
System Clock, java и System ClockOperations. Java — интерфейсы. System С lock, Java
(рис. 7.2) представляет собой интерфейс SystemClock.
1 package com.deitel.advjhtpl.idl.clock;
2
Э
4 /**
5 * com/deitel/jhtp4/idl/clock/SystemClock.Java
6 * Generated by the IDL-to-Java compiler (portable), v."3.0"
7 * from systemclock.idl
8 * Wednesday, February 28, 2001 8:24:01 FH PST
9 */
10
11 public interface SystemClock extends SystemClockOperations,
CORBA. Часть 1
319
12 org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity
13 (
14 } // интерфейс SystemClock
Рис. 7.2. Интерфейс lava, созданный idlj
В строках 11 и 12 объявлен интерфейс SystemClock и три интерфейса, от
которых наследует SystemClock. Два из трех интерфейсов являются определенными
в CORBA типами, от которых должны наследовать все CORBA-совместимые
объекты, org.omg.CORBA.Object и org.omg.CORBA .portable. IDLEntity. Третий
интерфейс — SystemClockOperationa (рис. 7.3) — создается на основе IDL-описания
и объявляет открытые операции данного сервера. SystemClock наследует от
SystemCIockOperations, кроме этого ничего там нет. SystemClock определяет
базовый класс, который наследует от SystemCIockOperations и от
CORBA-интерфейсов, упомянутых ранее. Это часть структуры, необходимой производным классам,
чтобы быть настоящими CORBA-объектами. SystemCIockOperations, ни от кого не
наследуя, объявляет единственный метод cnrrentTimeMUiis, изначально
определенный в IDL. _SystemClockImplBase наследует от SystemClock, еще одного
CORBA-интерфейса (InvokeHandler) и от базового класса CORBA-реализации
называемого org.omg.CORBA.portable.Objectlmpl (Impi — сокращение от
«implementation» — реализация). Серверу требуются эти три объектных типа, чтобы
унаследовать структуру и поведение, необходимые CORBA-совместимому
распределенному объекту. Интерфейс InvokeHandler объявляет метод invoke, который
является реализацией _SystemClockImplBase. ORB использует метод invoke для
вызова различных методов SystemClock в обобщенном виде. Метод _System-
ClocklmplBase invoke создается IDL-компилятором на основе IDL-интерфейса,
объявленного в IDL-файле systemclock.ldl.
1 package com.deitel.advjhtpl.idl.clock;
4 /**
5 * com/deitel/advjht^l/idl/clock/SystemClockOperations.Java
6* Generated by the IDL-to-Java compiler (portable), v. "3.0"
7 * from ayetemclock.idl
8 * Sunday, July 1, 2001 10:36:53 PM PDT
9*/
10
11
12 // Определение CORBA-совместилого сервиса
13 public interface SystemCIockOperations
14 (
15 long currentTimeMillis {);
16 ) // интерфейс SystemCIockOperations
Рис. 7.3. Интерфейс SystemCIockOperations, созданный idlj
7.3.2. SystemClocklmpl-java
SystemClocklmpi (рис. 7.4) является реализацией интерфейса SystemClock.
В соответствии с соглашением конкретный класс, реализующий открытый
интерфейс распределенного CORBA-объекта, имеет имя <JDL interface name>lmpl. Этот
класс не обязан объявлять метод main, но может это сделать.
1 // Рис. 7.4. SystemClocklmpl.java
2 // Реализация сервиса SystemClock
3
4 package com.deitel .advjht.pl. idX.clock;
5
6// Пакеты CORBA OMG
7 import org.omg.CORBA.ORB;
5 import org.omg.CosNaming.*;
9 import org.omg.CosNaming.NamingContextPackage.*;
10
11 public class SystemClocklmpl extends _Syst«mClockImp IBese {
12
13 // возвращаем текущее время в миллисекундах
14 public long currentTimettillisO
15 (
16 return System.currentTimeMillieO ;
17 J
18
19 // инициализирует объехт SystemClocklmpl вызовом метода register
20 public SystemClocklmpl( String params[] ) throws Exception
21 i
22 register( "TimeServer", params >;
23 J
24
25 // регистрирует объект SystemClocklmpl в сервисе именования
26 private void register( String согЬаЫа—, String params[] )
27 throws org.omg.CORBA.ORBPackege.lnvalidName,
28 org.omg.CosNaming.NamingContextPackage.invalidName,
29 CannotProceed, NotFound
(
// Проверяем име сервиса. Если кия пусто ияи null,
// ие пытаемся подключиться к сервису именования
if ( ( corbaHame = null ) ||
( corbaHame-trim().length() -= 0 ) )
throw new IllegalArgumentSxception(
"Registration name cannot be null or blank." );
// создаем и инициализируем ORB
ORB orb = 0RB.init( params, null );
// регистрируем объект в ORB
orb.connect( this );
II ищем сервис именования
org.omg.CORBA.Object corbaObject =
orb.reaolve_initlal_references( "NameService" J;
NamingContext naming =
NamingContextHelper.narrow( corbaObject J ;
// создаем массив HameComponent с информацией о пути,
// чтобы найти объект
NameComponent namingComponent =
new HameComponent( corbaHame, "" J;
NameComponent path[] = { namingComponent ) -,
II связываем объект SystemClocklmpl с ORB
CORBA. Часть 1
321
57 naming.rebind( path, this J;
58 System.out.printlnf "Rebind complete" ) ,-
59 J
60
61 // метод main, реалиаукяий сервер
62 public static void mai-nf String[] args ) throws Exception
63 (
64 // создаем CORBA-объект SyetetiClock
65 SystemClock timeServer = пей SystemClocklmpI{ args );
66
67 // хдеи запросов меене
68 java.lang.Object object = new java.lang.Object () ,-
69
70 // фиксируем сервер и рабочем режиме
71 synchronized( object ) (
72 object.vaitO;
73 >
74 J
75 } // завершение класса SyetemClocklmpl
Рис ТА. Реализация сервера SystemClock
В строках 7-9 находятся обычные операторы import. Мы используем классы
вне пространства имен java.lang, поэтому включаем их в файл Java. Обсуждение
классов, объявленных в операторах import, приведено ниже.
Метод main (строки 62-74) запускает сервер SystemClock. В строке 65
создается экземпляр объекта SystemClocklmpI. Конструктор SystemClockImp] (строки
20-23) осуществляет начальную инициализацию. Мы создаем экземпляр
SystemClocklmpI так же, как любой другой объект Java, и в данном примере мы никак
этот объект не используем (на самом деле, созданный объект ждет входящих
клиентских запросов). В строке 68 создается объект, который мы можем
заблокировать и вызвать принадлежащий его программному потоку метод wait (строка 72).
Использование ключевого слова synchronized (строка 71) означает, что ни один
другой объект не сможет получить доступ к объекту, пока выполняется блок
synchronized. В строке 72 осуществляется сохранение объекта в очереди потока,
пока кто-нибудь не вызовет метод notify. Объект timeServer может вызвать notify
в пределах своей области видимости, но, потенциально, это привело бы к
завершению работы сервера после завершения обработки входящего запроса.
Конструктор SystemClocklmpI может возбуждать исключения типа Exception.
В этом примере мы даем возможность конструктору возбуждать Exception; это
вряд ли целесообразно, но правил не нарушает. Входной параметр params
является массивом элементов типа String, которые могут содержать данные,
необходимые для работы сервера. Метод register принимает массив params и строку
"TimeServer" для инициализаций сервиса SystemClock.
В строках 26-59 содержится определение метода register. Помимо сервиса
SystemClock, принимающего имя (содержащееся в переменной corbaName) и
любые имеющие значения параметры конфигурации (params), этот метод может
генерировать одно из четырех исключений:
• InvalidName, возбуждаемоз ORB,
• InvalidName, возбуждаемоз сервисом именования,
• CannotProceed,
• Not Found.
11 3*» 204
322
Глава 7
Брокер объектных запросов или сервис именования могут возбудить одно из
указанных исключений, основываясь на взаимодействии с сервером. В нашем
случае брокер объектных запросов выполняется в адресном пространстве процесса
сервера SystemCIock. Сервер создает экземпляры объектов, необходимые для
выполнения стоящих перед ним задач, и понятия не имеет, какой тип брокера
объектных запросов используется. Так как стандарт CORBA не предопределяет
реализацию брокера объектных запросов, то брокер объектных запросов может быть как
локальным объектом, обеспечивающим возможность сетевого взаимодействия, так
и посредником процесса-демона (фонового процесса, работающего в своем
собственном адресном пространстве), выполняющего ту же задачу. Сервис именования
представляет собой отдельный процесс, у которого сервер регистрируется, чтобы
клиенты могли его найти.
В строках 35-36 возбуждается исключение Java IHegalArgument Except ion,
если входной параметр corbaName имеет значение null или не имеет значения
(проверка осуществляется в строках 33-34). Сервер не может быть
зарегистрирован и использован другими объектами, если у него нет имени, — эта ошибка
достаточно серьезна.
В строке 39 осуществляется вызов статического метода с запросом на создание
нового объекта брокера объектных запросов с использованием входных
параметров. Корректный входной параметр — это опция командной строки, указывающая
брокеру объектных запросов, какой коммуникационный порт использовать
(например, ORBInitialPort). Существует также версия init, не имеющая параметров,
которая возвращает брокер объектных запросов, создаваемый по умолчалию,
вместо брокера объектных вопросов, сконфигурированного в соответствии с входными
параметрами. Параметры метода init могут иметь значения null.
В строке 42 объект передается брокеру объектных запросов. Теперь все
обращения к серверу осуществляются через брокер объектных запросов. Брокер
объектных запросов (в зависимости от реализации) отвечает за выравнивание нагрузки,
обеспечение безопасности, фильтры и т.д. Кроме того, брокер объектных запросов
отвечает за базовые операции с протоколом нижележащего уровня — детали
каждого созданного класса ImplBase будут варьироваться в зависимости от его
IDL-описания (вызываемые методы и включаемые объектные типы могут
меняться в ззвисимости от типа сервера), но функциональность, конечно, остается той же
самой. Брокер объектных запросов вызывает метод _invoke (относится к классу
_System Clock ImplBase); _invoke отвечает за маршалинг входных параметров
(преобразуя их в формат, приемлемый для сетевого транспорта) и демаршалинг
возвращаемых значений (преобразуя значения обратно в требуемую форму). После
связывания реализованного объекта с объектным брокером, доступ к этому
объекту возможен только через брокер объектных запросов.
Сервер может предоставить клиентам возможность доступа к себе несколькими
способами. В строках 45-48 демонстрируется самый простой способ сделать
распределенный объект доступным для клиентов. Сервер SystemCIock
(реализованный в SystemClocklmpl) регистрируется в нескольких службах каталогов, к
которым могут обращаться клиенты в процессе поиска сервисов. Служба каталогов
решает те же задачи, чти и файловая система — она поддерживает в приемлемой
форме список ресурсов и их местоположение. Стандартной службой каталогов
CORBA является сервис именовании {Naming Service), который составляет список
ресурсов для будущего их использования клиентами. Факт регистрации в сервисе
именования не означает, что сервер может решать какие-то задачи; сервер просто
предоставляет клиентам возможность доступа к нему. Сервис должен быть готов
в любой момент обрабатывать входящие запросы на выполнение функций,
необходимых вызывающим клиентам. Процесс запуска сервера, или раскрутки, может
оказаться неоднозначным — каким образом сервер сможет обнаружить сервис
CORBA. Часть 1 323
именования при первом запуске? Существует ли некий определитель сервиса
именования?
Эту проблему за нас решает брокер объектных запросов — метод resolveini-
tial_references имеет доступ к специальному списку сервисов, непосредственно
доступных данному брокеру объектных запросов. Строка "NameService"
является стандартным именем, определенным в спецификации CORBA (вместе со
списком других имен1). Брокер объектных запросов имеет эффективный
мини-сервис именования, с помощью которого он может выполнять обнаружение базовых
сервисов.
В строках 45-46 извлекается объектная ссылка на сервис именования. Однако
чтобы метод resolve_initial_reference мог работать с любым из озрвнсов, ои
возвращает объектную осылку в виде объекта типа org.omg.CORBA.Object. В строках
47-48 используется статический метод narrow класса NamingContextHelper для
преобразования возвращенной объектной ссылки в объект нужного типа (в данном
случае, в тип NamingContext). Метод narrow является CORBA-механизмом
надежного приведения ссылки одного типа к ссылке другого типа. Метод narrow
проверяет, что интерфейс, которому мы пытаемся передать целевой объект, поддерживает
целевые объекты этого типа. Обычное приведение не работает с объектными
ссылками CORBA, так как объектная ссылка — это посредник на пути к удаленной
информации. Все классы Helper имеют метод static narrow, который дает нам
возможность осуществлять приведение родительских классов к производным классам.
В строках 52-54 создается объект NameComponent, после чего он помещается
в массив. Чтобы имя было правильно зарегистрировано, ресурс должен установить
контекст именования. Сервер регистрируется в основном (или корневом)
контексте именования, поэтому единственное, что нужно сервису именования, — зто имя
сервера, которое сервер посылает (имя передается в виде параметра метода register
в строке 57) с помощью метода rebind. Метод rebind вводит NameComponent, если
он отсутствует в сервисе именовал ия, или вводит его повторно, если он уже
присутствует там. Однако такое использование rebind позволяет за одно обращение
получить доступ только к одному экземпляру SyetemClock (NamingContext
жестко запрограммирован на входное имя corbaName и каждый выполняемый
экземпляр будет использовать одно и то же имя). Последним зарегистрированным
SystemClock является сервер, с которым связаны клиенты.
Таким образом, IDL описывает сервер, компилятор создает необходимые
вспомогательные файлы на Java, a SystemClocklmpl реализует сервер.
7.3.3. SystemClockClient.java
SystemClockCIient (рис. 7.5) представляет собой клиент, который соединяется
с SystemClock. Исходные функциональные возможности SystemClockCIient
заключаются в методе лш (строки 58-80). Этот объект соединяется с сервисом
SystemClock, запрашивает текущее время и отображает строку в JOptionPane.
Каждый раз когда пользователь щелкает иа кнопке ОК, клиент запрашивает
текущее время у сервера и отображает новое значение времени. Когда пользователь
щелкает на кнопке Cancel, клиентское приложение завершает свою работу.
Метод connectToTtmeServer (строки 29-48) возбуждает те же исключения
(InvalidName, Not Found и Cannot Proceed), что и метод register класса
SystemClocklmpl. И клиент, и сервер возбуждают одни и те же исключения, так как ло-
1 The Common Object Request Broker Architecture and Specification, p. 4-23, Editorial
Revision: CORBA 2.4.2: February 2001. (Спецификация и архитектура общего брокера
объектных запросов)
гика, используемая клиентами для чтения данных в сервисе именования, похожа
на логику, применяемую сервером для записи в нее данных.
В строке 35 вызывается статический метод init класса ORB для создания
брокера объектных запросов. Массив рагаша типа String позволяет динамически
настраивать брокер объектных запросов во время выполнения (массив был передан
методом main, который получил массив типа String из командной строки). После
того как клиент получает ORB, в строках 37-38 вызывается метод reeoIve_ini-
tial_references этого брокера объектных запросов, чтобы получить объектную
ссылку на сервис именования. В строках 39-40 осуществляется приведение
полученной объектной ссылки к объекту NamingContext с помощью метода Naming-
ContextHclper .narrow.
1 // Рис. 7.5. SyatemClockClient.java
2 // Клиентское приложение для SystemClock
3
4 package com.deitel.advjhtpl.idl.clock;
5
6 // Базовые пакеты Java
7 import Java.text.DateFormat;
8 import java.util.*;
9
10 // Пакеты |
11 import javax.s'
12
13 // Пахетм OHG CORBA
14 import org.omg.COBBA.OBB;
15 import org.omg.CosNaming.*;
16 import org.omg.CosHaming.NamingContextPackage.*;
17
IB public class SystemClocxClient implements Runnable {
19 private SystemClock timeServer;
public SystemTimeClient( String parens[] ) throws Exception
1
connectToTimeServer( params );
atartTimer();
}
// использование НашаService для соединения с сервером времени
private void соппесtToTimeServer( String parens[] )
throws org.omg.CORBA.ORBPackage.InvalidName,
org.omg.CosHaming.NamingContextPackage.InvalidName,
HotPound, CannotProceed
(
// соединяемся с сервером SystemClock
ORB orb = ORB.init( parens, null J;
org.omg.CORBA.Object corbaObjact =
orb.resolve_initial_references( "NansService" );
NamingContext naming =
NamingContextHelper.narrow( corbaQbject );
// определяем по объектной ссилне имя
NameComponent nameComponent =
пей NameComponent( "TimeServer", "" );
CORBA. Часть 1 325
45 NameComponant path[] = { nameComponent );
46 corbaObject = naming.resolve( path ) ,-
47 timeServer = SystenClockHelper.narrow( corbaObject );
48 J
49
50 // запускаем программный поток таймера
51 private void startTimerO
52 (
53 Thread thread = new Thread! this );
54 thread.start();
55 >
56
57 // регулярно обращаемся к серверу и отображаем результата
58 public void run()
59 (
60 long time = О;
61 Date date = null;
62 DateFormat format «
63 DateFormat.getTia»Xnstance( DateFormat.LONG );
64 String timeString - null;
65 int response = 0;
66
67 while( true ) (
68 time = timeServer.currentTineHillisO ;
69 date = new Date( time );
70 time String * format, format ( date ) .-
71
72 response = JpptionPane.showConfirmDialog( null, timestring,
73 "SystemClock Example", JOptionPane.OK_CANCEL_OPTION );
74
75 if ( response *= JOptionPane.CANCEL_OPTTON )
76 break; // Выходим отсюда
77 >
78
79 System.exit( 0 J ;
80 )
61
82 // метод main, выполняющий клиентское приложение
83 public static void mein( String args[] ) throws Exception
84 (
85 // создаем клиента
86 try {
87 new SystemCloekCliant( args );
88 )
89
90 // обрабатываем исключения в процессе работы клиента
91 catch ( Exception exception ) (
92 Syetern.out.println(
93 "Exception thrown by SystemCloekClient:" );
94 exception. printStackTraoe (J ,-
95 J
96 >
97 ) // завершение класса SyatemClockClient __
Рис. 7.5. Клиент, который подключается к SystemClock (часть 1J
326 Глава 7
Рис. 7.5. Клиент, который подключается к System С lock (часть 2)
Клиент должен запросить у сервиса именования объектную ссылку на сервис,
который он разыскивает, — сервис SystemClock, имеющий имя "TimeServer"
(объявлен в классе SystemClocklmpl).
Хороший стиль программирования 7.2
Сохраняйте имена сервисов в файлах .properties вместо того, чтобы
«зашивать» их в исходный текст класса. Включение имен в исходный текст
класса требует дополнительного сопровождения файлов классов при
изменении имен. Файл .properties дает возможность настраивать уже
развернутую систему, тем самым делая настройку вопросом
администрирования, а не разработки,
В строках 43-44 создается объект NameComponent, В SystemClocklmpl (рис. 7.4)
мы используем объект NameComponent для регистрации в NamingContext путь
к расположению SystemClock. В данном случае SygtemClockClient использует
объект NameComponent, чтобы узнать у NamingContext расположение сервиса
с конкретным именем. В строках 45-46 объект NameComponent помещается
в массив, затем массив передается NamingContext с помощью его метода resolve.
Этот метод возвращает объект типа CORBA Object, поэтому мы используем
статический метод SystemClockHelper.narrow, чтобы осуществить приведение
объектной ссылки к требуемому типу производного класса. С этого момента у клиента
есть активный распределенный объект, клиент вызывает этот распределенный
объект, когда пользователь щелкает на кнопке ОК.
7,3.4. Выполнение примера
Прежде чем выполнять рассмотренный выше пример, убедитесь, что на рабочей
станции установлен JDK 1.3 и в переменную окружения PATH включен каталог
ЛЖ bin.
Для выполнения примера SystemClock требуется следующая
последовательность действий:
1. Скомпилировать IDL-файл с помощью idlj.
2. Реализовать и скомпилировать серверный класс.
3. Реализовать и скомпилировать клиентский класс.
4. Запустить сервис именования.
5. Запустить сервер.
6. Запустить клиент.
Для компиляции IDL-файла из командной строки используется компилятор
idlj, поставляемый вместе с ЛЖ. Неполный список опций командной строки idlj
выглядит так:
• — f<client | server | all>
• -pkgPrefix <имя модуля или тип IDL> добавляемый префикО
• —td <выходной каталог>
CORBA. Часть 1
327
Опция — f компилятора idlj управляет созданием кода для заглушек и скелетов.
Опция — fclient создает только клиентские файлы, — feerver — только серверные
файлы, a —fall — и те, и другие. Опция — pkgPrefix генерирует имена пакетов. Эта
опция используется с именем модуля. При компиляции модуля с именем mod-
Name с опциями —pkgPrefix modName prefix будут созданы файлы Java с именем
пакета preflxjnodName, Опция — td предписывает idlj записывать созданные
файлы в указанный каталог.
Например, если исходный код находится в каталоге C:\src, командная строка
будет выглядеть следующим образом (исходя из IDL-опнсания, представленного на
рис. 7.1):
idlj -pkgPrefix clock com.deital.advjhtpl.idl -td c:\src
-fall SyetemCloek.idl
Эта командная строка создает и серверные, и клиентские файлы CORBA-Java.
После реализации клиента и сервера скомпилируйте клиентский и серверный код.
Для этого примера достаточно кода, представленного на рис. 7.4 и 7.5 (System-
Clocklmpljava и SystemClockCIient.java).
Java 2 включает tnameserv — базовую реализацию CORBA Object Service (COS)
Naming Service, tnameserv не является готовым средством, реализующим сервис
именования; скорее оно служит средством тестирования, позволяющим проверить
правильность взаимодействия клиентов и серверов. Сервис именования должен
быть приведен в активное состояние до того, как будет запущен сервер (System-
Clocklmpl). Запустить tnameeerv в виде фонового процесса (в Windows, надо
запустить tnameserv в отдельном окне командной строки; в UNIX нужно просто
добавить знак амперсанда в конце командной строки при запуске tnameeerv):
tnaaeserv -ORBlnitialPort 1050
Все запускаемые в этом примере процессы используют для свлзи порт 1050.
Портом по умолчанию для сервера имен, □оставляемого с Java 2, является порт
900 — однако иногда этот порт доступен только для администраторов.
Запускаем SystemClocklmpl в виде отдельного процесса:
java com.deltel.advjbtpl.idl.clock.SystemClocklmpl
-ORBlnitialPort 1050
Таким же образом запускаем S у stemClock Client:
Java com.daitel.advjhtpl.idl.clock.SystemClockClient
-ORBXnitialPort 1050
. После отображения графического пользовательского интерфейса в клиентском
приложении щелчок на кнопке ОК приводит к запросу времени на сервере и
отображению строки со значением времени. Первый пример Java/CORBA завершен.
7.4. Обзор архитектуры
Основные концепции CORBA, обсуждавшиеся до сих пор, должны быть близки
тем равработчикам, кто хорошо знаком с паттернами проектирования. Заглушки
и скелеты являются посредниками (объектами, управляющими доступом к другим
объектам). Сервисы, возвращающие объекты, являются фабриками (объектами
создающими объекты, но перекладывающими их реализацию на производные
классы). В CORBA фабрики и посредники могут быть обнаружены повсюду. Те
классы и объекты, которые используют паттерны проектирования, скрывают от
разработчиков детали реализации.
328 Глава 7
j^h Хороший стиль программирования 7.3
||У | Паттерны проектирования унифицируют проектные решения и
терминологию. Изучение паттернов проектирования помогает разработчикам
повторно использовать проекты, компоненты, сервисы и структуры.
Брокер объектных запросов (ORB) — основной компонент CORBA. Все CORBA-
совместимые объекты должны иметь брокер объектных запросов между ними
и всеми, кто к ним обращается (рис. 7.6). Брокер объектных запросов должен
иметься в CORBA-совместнмой распределенной системе.
Путь вызова а действительности
Клиент Сервер
Путь вызова с точки зрения клиента I Т
Клиент ч Сервер Заглушка Скелет
| ORB | »] ORB ~|
Рис. 7.6, Путь вызова от клиента к удаленному обьекту
ORB можно рассматривать как коммутационную плату (или коммуникационную
шину) распределенных систем. Что касается коммуникационной шины, то суть
здесь заключается в том, что все объекты, использующие брокер объектных
запросов, могут взаимодействовать с любыми другими объектами, поднлючениыми через
брокер объектных запросов. В чем преимущество использования брокеров
объектных запросов? Брокеры объектных запросов представляют собой гибкую
конструкцию, но какие все же они решают проблемы? На оба эти вопроса консорциум OMG
дает ответ в Object Management Architecture (архитектуре управления объектами).
Типичная программная система призвана удовлетворять различные
потребности. Унаследованные системы (то есть любые системы, разработанные и
установленные ранее) обычно являются изолированными, сосредоточенными и не
предназначены для совместного использования информационных ресурсов. Они решают
узкую задачу, даже если решение требует больших объемов данных. Со временем,
по мере модификации систем, их модернизация обходится все дороже. За последние
30-40 лет эти системы стали практически несовместимыми. Развитие аппаратных
и программных средств, изменение способов ведения бизнеса привели к появлению
множества несовместимых систем, пока системные интеграторы не занялась
проблемой их связи. Переход от частных решений к интеграции лишь подчеркнул
необходимость осуществлять интеграцию не за счет дублирования усилий, а за счет
новых решений. Консорциум OMG, вместе с компаниями-производителя ми,
являющимися членами OMG, разработал Object Management Architecture (рис. 7Л).1
Object Management Architecture (ОМА) — это эталонная архитектура
распределенных систем, основанная на концепции брокера объектных запросов. Используя
концепции объектно-ориентированных технологий, ОМА определяет готовое к
использованию рабочее пространство, где объекты, по определению являющиеся
общедоступными, открыты для использования любым другим объектом или серей-
1 Object Management Group, «A Discussion of the Object Management Architecture»
www.omg. org/t echnology/doc nmente/ fо rm al/ ob j ect^mae agement_arcbitactnre.htm,
January 1997, p. 4-2, Fig. 4-1. (OMG, 'Обсуждение архитектуры управления объектами.,
январь 1997. с. 4-2, Рис. 4-1,)
CORBA. Часть 1 329
сом посредством объектного брокера. Объектный брокер — это прозрачный
коммуникационный механизм, обеспечивающий надежный обмен сообщениями
между объектами независимо от их местоположения. ОМА определяет
абстракцию, которая скрывает тот факт, что различные системы используют разные
языки программирования или несовместимые версии одного и того же языка.
Нестандартные, Интерфейсы Интерфейсы
прикладные интерфейсы предметных областей горизонтальных средств
Интерфейсы приложений Общие средства Интерфейсы доменов
брокер объектных запросов (ORB)
Объект ые сервисы
Общие интерфейсы сервисов
Рис. 7.7. Эталонная модель архитектуры управления объектами (Object Management
Architecture). С разрешения Object Management Group, Ine
CORBA определяет правила функционирования брокера объектных запросов
в условиях использования разных языков программирования. ОМА определяет
полиморфное рабочее пространство общих сервисов, которое выглядит однородным
извне (со стороны API), но может быть разнородным внутри.
Брокеры объектных запросов могут быть реализованы одним из двух способов:
в виде библиотек или как процессы-демоны. Ни для клиента, ни для серванта
средства реализации не имеют значения. Конструкция объекта брокера объектных
запросов скрывает лежащую в ее основе реализацию. Обычно клиент использует
брокер объектных запросов на основе библиотеки, а сервер использует ORB в виде
процесса-демона- Это деталь реализации, решение о которой принимают
администраторы систем. С позиции активного процесса ничего не меняется.
Брокер объектных запросов играет в ОМА основную роль. Теперь мы
рассмотрим, как клиент и сервант воспринимают брокер объектных запросов. Клиент
взаимодействует с брокером объектных запросов одним из трех способов: посредством
статической заглушки (создаваемой IDL-компилятором), динамического
интерфейса (используя АРГ динамических вызовов CORBA) или API брокера объектных
запросов. Концептуально брокер объектных запросов взаимодействует с сервантом
тремя способами: посредством статического скелета, динамического интерфейса
или объектного адаптера серванта (что выглядит так, как если бы брокер объектных
запросов взаимодействовал с сервантом напрямую). Сервант, обращающийся к
другому серванту, «становится» клиентом и функционирует как клиент.
Наиболее прямолинейным способом взаимодействия с брокером объектных
запросов является использование статических заглушек и скелетов. Они содержат
код, необходимый для связи, и предоставляют возможность статического
контроля типов на основе их IDL-описания. Динамические вызовы (и со стороны
клиента, и со стороны серваита) требуют больших издержек, но являются более
гибкими, так как позволяют разработчикам программно управлять вызовами
удаленных объектов (динамические вызовы обсуждаются в разделе 8.2). Выполнение
удаленных методов путем вызовов брокера объектных запросов напрямую возмож-
330
Глава 7
но, но не рекомендуется. Та опосредованность, которая придает CORBA ее силу,
в случае прямых вызовов брокера объектных запросов теряется и восстановить ее
в процессе реализации системы бывает трудно. Клиент и сервант взаимодействуют
с брокером объектных запросов, чтобы получить доступ к определенного вида
операциям, осуществить которые можно только через брокер объектных запросов —
операциям с объектными ссылками и доступом к репозиториям интерфейсов
и реализаций (двум информационным хранилищам метаданных). Низкоуровневые
решения такого рода в CORBA требуются только при реализации средств
поддержки инфраструктуры, таких как драйверы и мосты. Рис. 7.8 иллюстрирует
взаимодействие с объектным брокером1.
Клиенты Сервакты
Рис. 7.8. Струюура интерфейса запросов ORB. С разрешения Object Management Group, Inc.
Концепции CORBA, ре осматривавши ее я ранее, касались объектных
адаптеров — объектов, которые располагаются между клиентом и сервером, чтобы
управлять доступом к распределенному объекту. Объектный адаптер действует как
«соединитель» между клиентом и кодом серванта, который выполняется при вызове
операции (вездесущий брокер объектных запросов располагается между клиентом
и объектным адаптером). До появления CORBA 3.0 стандартным объектным
адаптером был Basic Object Adapter (базовый объектный адаптер) или BOA. BOA
упрощал соединение клиента и сервера. В CORBA 3.0 был определен другой
объектный адаптер названный Portable Object Adapter (РОА — переносимый объектный
адаптер). РОА заменил BOA как более предпочтительный. BOA не соответствовал
требованиям, которые предъявляют Internet-приложения. В то время, когда OMG
впервые специфицировал BOA, то, что сейчас рассматривается как стандартная
функциональность (т.е. возможность использования средств CORBA разных
производителей), тогда не воспринималось как первоочередная задача. Так же как Java
вытесняет различные технологии, спецификация CORBA заменила BOA на РОА.
РОА служит многим целям, включая возможность отделить доступ к серванту
от самого серванта. Потребность клиента в обслуживании означает, что ему нужны
конкретные услуги в конкретное время. Для удовлетворения этой потребности
несколько компонентов осуществляют совместные действия. Во-первых, у клиента
есть объектная осылка, представленная CORBA-объектом. В объектной ссылке
CORBA-объект содержит информацию о местонахождении создавшего данный
объект объектного адаптера. Объектный адаптер анализирует клиентский вызов,
принимая решение, какой объект (или сервант) может обработать данный вызов и.
1 The Common Object Request Broker Architecture and Specification, p. 2-3, Figure 2-2,
Editorial Revision: CORBA 2.4.2: February 2001 and Siege], Ph.D, Jon, CORBA 3, Second
Edition, New York, NY: Wiley Computer Publishing, 2000, p. 79. (Архитектура и
спецификация общего брокера объектных запросов.)
CORBA. Часть 1 331
соответствующим образом, завершает вызов (основываясь на различных опциях
конфигурации, задаваемых при создании объектного адаптера). Если клиент,
удерживая полученную им при первом подключении к серванту объектную
ссылку в течение длительного периода времени и не нуждаясь в обращениях к этому
серванту, то такое ожидание никак не скажется на самом серванте. Пострадает
масштабируемость, превращая С OR В А в неэффективное с точки зрения системной
интеграции решение. Благодаря отделению серванта от клиентских обращений
к сервису раапичные обслуживающие объекты (контролируемые с помощью
времени жизни объектов и паттернов активизации) могут обрабатывать вызовы
методов, обращенные к сервису в целом. Последнее утверждение заключает в себе
целый ряд аспектов. Опосредован нос ть, достигаемая использованием РОА, означает
прозрачное решение этих вопросов.
Динамический интерфейс, используемый клиентом и сервером, обсуждается
в главе 8. Статические заглушки, использующие Static Invocation Interface (SII),
имеют жестко запрограммированные объектные тины, чтобы осуществлять
проверку типов во время компиляции, тогда как динамические заглушки,
использующие Dynamic Invocation Interface (DII), осуществляют проверку типов во время
выполнения.
Сервисы CORBA (CORBAservices) — это базовые сервисы, доступные всем
объектам, подключенным к коммуникационной шине данного брокера объектных
запросов. Так как брокер объектных запросов является ядром CORBA-системы,
сервисы С OR В А могут требовать для правильного функционирования его наличия.
Всего имеется шестнадцать сервисов1:
1. Сервис имен (Naming Service)
2. Сервис управления событиями (Event Management Service)
3. Сервис жизненных циклов (Life Cycle Service)
4. Сервис устойчивых состояний (Persistent State Service)
5. Сервис транзакций (Transaction Service)
6. Сервис параллельного исполнения (Concurrency Service)
7. Сервис взаимоотношений (Relationship Service)
3. Сервис экспорта (Externalization Service)
9. Сервис запросов (Query Service)
10. Сервис лицензирования (Licensing Service)
11. Сервис управления ресурсами (Property Service)
12. Сервис времени (Time Service)
13. Сервис безопасности (Security Service)
14. Сервис уведомлений (Notification Service)
15. Сервис трейдинга (Trader Service)
16. Сервис коллекций (Collections Service)
Эти сервисы являются основными, спецификация Enterprise JavaBeans требует
обязательного использования четырех из них (имей, безопасности, устойчивых
состояний и транзакций). Все сервисы C0RBA имеют стандартные IDL-ннтерфейсы,
которые описывают предлагаемые этими сервисами услуги. Назначение
стандартных интерфейсов точно соответствует еще одной цели ОМА: подключаемые
сервисы должны иметь стандартные механизмы подключения.
CORBAservices: Common Object Services Specification, OMG. Updated December 1998
(Сервисы CORBA: спецификации общих объектных сервисов).
332
Глава 7
Средства CORBA (CORBAf utilities) — располагаются над промежуточными
сервисами CORBA и состоят из двух групп: горизонтальные и вертикальные
средства. Горизонтальные средства определяют функциональность клиента,
вертикальные средства — функциональность предметной области. Горизонтальные средства
CORBA имеют только три спецификации1:
1. средства мобильных агентов,
2. средства печати,
3. средства локализации.
С одной стороны все три средства достаточно обособлены, чтобы не
объединяться с сервисами CORBA, а с другой стороны достаточно абстрактны, чтобы не
конфликтовать с потенциальными производителями, предлагающими решения,
использующие сервисы CORBA.
Вертикальные средства CORBA, называемые также доменами или
прикладными областями CORBA {CORBA Domains), располагаются между сервисами CORBA
и объектами-приложениями (Applications Objects) (рис. 7.7). Они, используя
различные сервисы CORBA и горизонтальные средства, определяют сервисы
предметных областей. Одиннадцать рабочих групп по предметным областям занимаются
определением различных сфер их применения2:
1. корпоративные системы,
2. финансы /страхование,
3. электронная коммерция,
4. промышленность,
5. здравоохранение,
6. телекоммуникации,
7. транспорт,
8. исследования в области биологии,
9. сфера обслуживания,
10. управление, контроль, коммуникации, компьютеры и информация,
11. космос.
Комитеты OMG для различных отраслей промышленности создают,
совершенствуют, принимают спецификации. Прикладные области CORBA заслуживают
того, чтобы посетить сайт OMG (www.omg.org).
Объекты-приложения — самый верхний уровень ОМА. Говоря условно,
разработчики найдут здесь меньше всего кода, который может быть повторно
использован; объекты этого уровня предназначены для предметных областей, зависящих от
конкретных предприятий. Объекты-приложения реализуют такие функции,
которые отсутствуют на уровнях предметных областей, средств и сервисов CORBA.
Заказные приложения встраиваются в структуру ОМА, используя существующие
сервисы, определенные с помощью ШЬ-опнсаний, или совершенно новые сервисы,
созданные разработчиками в ответ на требования проекта.
Понимание ОМА является преимуществом при разработке архитектуры, так
как многие из составляющих, необходимых для большинства систем, в ОМА уже
определены. Распределенные системы сложны н содержательны по своей сути, но
основные концепции, независимо от масштаба системы, остаются неизменными.
1 CORBA Common Facilities Specifications, www.omg.org/technology/docameate/
2 Siegel, Ph.D. Jon. CORBA 3. Second Edition, New York, NY: Wiley Coioputer Publishing,
2000, page 377.
CORBA. Часть 1
7.5. Основы CORBA
Распределенные объекты должны быть определены таким образом, чтобы их
могли обнаружить и использовать другие распределенные объекты. Мы описываем
распределенные объекты на IDL и используем заглушки и скелеты, созданные
IDL-компилятором, чтобы обеспечить средства для удзленных вызовов.
Производители брокеров объектных запросов вместе со своими продуктами
поставляют IDL-компиляторы. Что касается Java 1.2, то Javasoft приспособила
библиотеки OMG для Java, поставляя их вместе с JDK. Доступность библиотек
CORBA позволяет разработчикам Java /CORBA -продуктов создавать собственные
заглушки и скелеты, используя поставляемый с Java IDL-компилятор idlj.
Теоретически, заглушки для Java, создаваемые компилятором, должны
взаимодействовать с кодом скелетов, работающим с брокерами объектных запросов других
производителей (код заглушек других производителей брокеров объектных запросов
также должен взаимодействовать с кодом Java-скелетов), но следует это проверять
и отдавать себе отчет в том, что возможна несовместимость.
Документ OMG formal/99-07-53 определяет IDL-Java отображение, охватывая
все вопросы от имен пакетов до классов Helper для отображения псевдообъектов
CORBA. Синтаксис IDL похож на синтаксис C++, но на этом похожесть и
заканчивается. На рис. 7.9 перечислены наиболее часто используемые отображения
спецификации1.
IDL
module
interface
struct
const
boolean
char
wchar
octet
*trinq
^f
unsigned short
long
unsigned lonq
lonq long
unsigned long long
float
double
fixed (не поддерживается в idlj)
sequence
I] (массив)
Java
package
interface
class
public static final
boolean
char
wchar
octet
Java.lanq.String
Java.lang.String
short
int
int
lonq
lonq
float
double
lava.math.BiqDecimal
[] (массив)
I] (массив)
Рис. 7.9. Типы и ключевые слова IDL и их соответствия ключевым словам lava
"IDL to Java™ Language Mapping Submission"
w .omg .org/cgi -hi n/ d oc ?form al /.
Пакеты, начинающиеся с org.omg, содержат пакеты Java, составляющие ядро
инфраструктуры CORBA. Производители средств CORBA поставляют собственные
версии библиотек CORBA со своими Java-брокерами объектных запросов.
Следующий пример перечисляет различные ключевые слова IDL и их
Java-аналоги. IDL-описание на рис.7.10 использует многие из ключевых словШЬ,
благодаря чему можно видеть, как ШЬ-компилятор преобразует ключевые сяова в
создаваемых им файлах, приведенных на рис. 7.11-7.13.
1/*
2 * Комментарии вне области объявления модуля игнорируются
3 • idl-компилятором. Этот комментарий на нескольких сорок
4 * но будет перенаем як в один из файлов, соадаваеинх idlj
5 */
б
7 // Этот однострочный комментарий также игнорируется lDL-хомпнляторои
8
9 module шарteat [
10
11 // Этот комментарий переносится в файлы, создаваемые для StructMap
12 struct StructMap (
13
14 // Этот комментарий появится в начале объявлений типов
15 boolean boolValue;
16 char chaxValua;
17 trcbar wChaxValue;
18 octet octetValue;
19 string stringValue;
20 «string wStringValue;
21 short ahortvalue;
22 unsigned short uShortValua;
23 long longValua;
24 unsigned long uLongValue;
25 long long longLongValue;
26 unsigned long long uIiongLongValue;
27 float floatValue;
28 double doubleValua;
29
30 // fixed fixedValue,- не поддерживается JavalDL
31 J;
32
33 typedef sequence <StructHap> StructMapSeq;
34 typedef sequence <StructMap, 5> BoundStructHapSeg;
35
36 typedef long IntArrayl 5 ] ,-
37
38 // Этот комментарий появится перед
39 // объявлением интерфейса interfaceNane
40 interface interfaceNane (
41
42 // комментарий перед атрибутом readwrite
43 attribute long anAttribute;
44 readonly attribute long roAttribute;
45 const long constantValue = 42;
46
47 // комментарий перед объявлением методов
48 void segtfethod( in StructHapSeq sag J ;
CORBA. Часть 1 335
49 void bounds• qMethod( in BoundstruсtMapSeq seq ) .*
50 void arrayMethod ( in IntArray array );
51 void intOutMethod( inout long intValua };
52 };
53 }; // эавердениа иодуля maptest
Рис. 7.10. IDL-файл, содержащий многие типы и ключевые слова IDL
Файл raaptest.idl начинается с двух комментариев разных типов:
многострочного (строки 1-5} и однострочного (строка 7}, Эти комментарии не переносятся в
файлы, создаваемые IDL-компилятором. Они предназначены для автора .idl фейла
и тех, кто с этим файлом работает, в качестве вспомогательной документации.
Комментарии из IDL-файл а переносятся в генерируемые файлы только в том случае,
если находятся в области модуля. Следовательно строки 1-5 и строка 7 из map-
test .idl являются посторонними для файлов .Java, но не для файлов .idl.
Имя модуля шар test (строка 9) напрямую отображается в имя пакета mapta3t
(однако, компилятор может обрабатывать префиксы имен пакетов с помощью
опции командной строки — pkgPrefix подобно тому, как мы это сделали в примере
SystemClock). Ключевое слово module и относящиеся к нему фигурные скобки
определяют самую высокоуровневую область видимости идентификаторов в ШЬ.
Комментарий в строке 11 переносится в генерируемый файл, так как включен
в тело модуля (таким образом может быть включена любая информация,
относящаяся к Java-коду}. В строках 12-31 объявляется структура StructMap.
StructMap использует большинство типов данных IDL, чтобы можно было наблюдать,
как они преобразуются в типы данных Java. Строка 31 завершает структуру
StructMap закрывающей фигурной скобкой и точкой с запятой.
Из таблицы со списком соответствий типов может показаться, что IDL не
оперирует с составными типами. На самом деле структуры (struct} допускают
агрегирование базовых типов и объектных ссылок. Однако так как структура на самом деле
не является классом (даже если она преобразуется в класс}, компилятор создает
public final класс, чтобы предотвратить создание производных классов на основе
данной структуры. Структура — это совокупность данных, компилируемых в
определение класса, который удаленный сервант может возвращать клиенту или
получать от него во время выполнения. В обоих случаях принимается локальная
копия соответствующих данных (а не объектная ссылка}.
Большинство базовых типов при преобразован ни из IDL в Java остаются
неизменными. IDL-компилятор отображает знаковые типы данных на соответствующие
знаковые типы Java; беззнаковые типы IDL подвергаются риску быть усеченными, так
как преобразуются в свои знаковые аналоги в Java (тип signed short не может
хранить такое же большое значение, что и тип unsigned short}. IDL-компилятор
осуществляет также инициализацию переменных, устанавливая их в целочисленный ноль,
0.0, false или null (выполняя, где нужно, преобразование типа}. Заметьте, что в
созданном Java-коде StructMap.java (рис. 7.11) объявленные экземпляры переменных
являются открытыми. В коде, создаваемом компилятором ШЬ, нарушается
инкапсуляция. Действительно, в ГОЬ все является открытым, так как С, COBOL и другие
IDL-coвместимые языки не поддерживают концепции инкапсуляции.
Ш Общая методическая рекомендация 7.3 _^___^__
В качестве способа восстановления инкапсуляции используйте
интерфейсные классы как оболочки объектов-структур. Интерфейсные классы
описывают объекты, которые служат посредниками в организации
доступа к другим объектам через четко определенные интерфейсы API. При
создании экземпляра объекта, представляющего структуру CORBA,
передавайте его в конструктор интерфейсного объекта.
336
Глава 7
В блоке interface (строка 40) объявляются атрибуты, которые клиенты могут
запрашивать, и методы, которые клиенты могут вызывать у удаленных объектов.
Структура (struct) описывает фрагмент данных времени выполнения, а интерфейс
(interface) описывает удаленный объект, который может находиться в адресном
пространстве другого процесса, но выглядит как локальный объект. Интерфейс
является представлением удаленного объекта, так как IDL-компилятор не реализует
распределенный объект. Разреботчик может взять сгенерированный код и
реализовать настоящий сервант, используя сгенерированные определения. Два из
созданных интерфейсных файлов (InterfaceNameOperations.java и InterfaceName.java)
помогают разработчикам (1) структурировать сервант таким образом, который
позволяет осуществлять полиморфный доступ к данному серванту через родительский
интерфейс (InterfaceName), и (2) наследовать нужный нам режим распределенного
протокола от абстрактного класса (_InterfaceNameImpIBase).
Интерфейс определения распределенного объекта InterfaceName содержит два
атрибута (attribute), одну константу и четыре операции, В строке 43 объявляется
attribute, который можно читать и изменять, тогда как в строке 44 в явном виде
задается атрибут roAttribute, доступный только для чтения. Ключевое слово const
в строке 45 объявляет поле cons taut Value неизменяемым.
Только интерфейс может содержать объявления атрибутов. Если ключевое
слово attribute используется отдельно, компилятор генерирует два метода: аксессор
(accessor, метод get) и мутатор (mutator, метод set). Использование ключевого
слова readonly вместе с attribute приводит к созданию только аксессора.
Изначально, методы-аксессоры были похожи на методы get в стиле JavaBean (getAge,
get At tribute). В последней версии спецификации IDL соглашение об именовании
JavaBean существенно сокращено. Перегруженные методы создаются и для
аксессора, и для мутатора с использованием названия атрибута и различаются своими
сигнатурами: сигнатура метода-аксессора через attribute balance возвращает
значение, тогда как мутатор принимает входной параметр и ничего не возвращает.
Вывод такой: CORBA-объекты не могут быть JavaBeans-объектами, однако
рассуждения на эту тему будут продолжены в разделе, посвященном компонентам
CORBA (CORBAcoinponents), в главе 8.
В IDL ключевое слово const объявляет константу. С точки зрения синтаксиса
в Java значен и е-константа объявляется с помощью ключевого слова final.
Идиоматически, ключевые слова Java static и final объявляют константы, так как нет
смысла иметь несколько копий неизменяемого значения. ШЬ-компилятор
выполняет генерацию кода с учетом идиом Java (рис. 7.13, строка 14). ЮЬ-компилятор
учитывает также тот факт, что целочисленные значения в Java представлены
типом long, и поэтому преобразует символ к типу с меньшей разрядностью.
Отображение методов, режимы передачи параметров, объекты Holder и массивы
представляют собой концепции, необходимые для определения объектов на стороне
сервера и тех сервисов, которые они реализуют. Разработчики, использующие Java,
применяют многие из этих средств, чтобы обеспечить кроссплатформенность
данных и механизмов вызова операций, реализуемых объектами. IDL-отображения
CORBA предоставляют те же гарантии для данных и вызовов между любыми
языками, поддерживаемыми CORBA.
Обратите внимание на использование ключевого слова in в объявлении метода
на рис. 7.10, строка 48. IDL использует ключевые олова in, out и inout для
описания параметров методов. Если переменная объявлена как in, то вызывающему
методу передается ее копия. Область действия изменений, осуществляемых
сервантом, доступна только данному серванту (похоже на вызов по значению). После
завершения работы метода клиент не заметит никаких изменений. Переменная out
должна быть ссылкой на Java-объект, содержащий другой Java-объект, который
может быть замещен еще одним Java-объектом и это изменение будет доступно
CORBA. Часть 1 337
клиенту (похоже на вызов но ссылке). Переменная, объявленная как inoat,
использует и ту, и другую семантику. В Java есть два типа значений: базовые типы
и объектные ссылки. Объектная ссылка по определению соответствует вызову по
ссылке. С другой стороны, базовый тип используется только в качестве значения.
CORBA использует объекты Holder, чтобы сделать изменения значений объектом
на стороне сервера похожими и на базовые типы и на ссылки. Объект Holder
может изменять свои значения, а клиенту будут доступны эти изменения.
Фактически это работает подобно объекта м-оберткам Java, например. Number, которые
являются объектными аналогами базовых типов, однако, Java-объекты Number
являются неизменяемыми, тогда как объекты Holder являются изменяемыми.
Каждый раз, когда объявляются структура или интерфейс, IDL-компилятор
генерирует связанный с ними класс Holder на случай, если структура или интерфейс
являются переменными inout или out. Если серванту нужно послать некоторое
значение клиенту (или базового типа, или объекта), посылайте это значение в виде
out (или inout) переменной и используйте объекты Holder для их надежной
передачи через сеть. Все примитивный тины Java имеют классы Holder, доступные
в пакете org.omg'.CORBA, и IDL-ком пиля тор генерирует правильную сигнатуру
метода в интерфейсе Operations, определяя требуемый объект Holder.
В строках 33 и 36 представлены два способа объявления массивов в IDL: с
использованием ключевого слова sequence и открывающей и закрывающей
квадратных скобок ([]). Ключевое слово sequence или квадратные скобки ([]) в любом
случае не используются сами по себе. Автор IDL-описания должен объявить
определение типа (typedef) с использованием sequence или [] для объявления массива как
простого имени. Например, тип StructMapSeq является sequence (массивом) типа
struct StructMap.
-_^-i Типичная ошибка программирования 7.1
&р Символьная последовательность (sequence) <StructMap> не может опре
J делять идентификатор массива; IDL-компилятор требует, чтобы иден
тификатор был определен с помощью typedef для типов массивов, иначе
компилятор выдает синтаксическую ошибку. Переименование
объявления массива преобразует новый идентификатор в дополнительный объ-
Ключевое слово sequence может использоваться одним из двух способов; как
ограниченная последовательность sequence или неограниченная. Когда длина
задана, последовательность рассматривается как ограниченная (строка 34); в
противном случае последовательность рассматривается как неограниченная (строка 33).
И в том, и в другом случае компилятор IDL-Java раскрывает typedef в обратном
направлении вплоть до фактического объявления массива и генерирует типы
Helper и Holder для соответствующих типов массивов (в данном случае Struet-
MapSeqHclper, StractMapSeqHotdcr, BoundStructMapSeqHelper и Bounds true t-
MapSeqHotder). Мы рассмотрим объекты Holder, когда будем обсуждать режимы
передачи параметров.
Стандартная запись с квадратными скобками также может использоваться для
объявления массивов. Однако применение IDL-массивов не соответствует нормам
Java. В Java размеры не являются частью внутреннего определения типа данных.
ВIDL новый массив (с использованием []), определенный с помощью typedef,
имеет ограниченный размер, заданный посредниками заглушка/скелет. Ключевое
слово typedef определяет массив конкретного типа данных с помощью имени
нового типа данных и размера массива. В IDL typedef похож на typedef в С и C++ —
новый тип данных выглядит как существующий, но этот тнп данных является
псевдонимом, используемым компилятором. Применение typedef позволяет
разработчику IDL-описаний определять новые типы данных без оглядки на вопросы.
связанные с реализацией. В maptest.idl (рис. 7.10) массив структур Struct Map
объявляется с использованием ключевого слова typedef, типа для
переопределения и нового имени, заключенного в угловые скобки <> (строки 33 и 34). Размер
массива объявляется в имени, но больше нигде не используется; массив является
ограниченным.
Рис. 7.11 представляет собой листинг Struct Map. Java одного из файлов,
сгенерированных на основе maptest.idl. В первой строке (это имеет место для всех
сгенерированных файлов) объявлено имя пакета шар test. Строки 12 и 17 являются
комментариями, приведенными на рис. 7.10 в строках 11 и 14. Те, кто создают
и сопровождают IDL-описания, отвечают за включение информации об IDL и его
назначении, а не о конкретных деталях реализации, которые могут изменяться.
1 package m&ptest;
' шар tee t/S true Шар. Java
' Generated by the XDL-to-Java compiler (portable),
' from Bapteet.idl
' Monday, Hay 14, 2001 4:18:09 PM PDT
12 // Этот комментарий появляется в файлах, создаваемых ;
13 public final class StructHap implements
14 org.omg.CORBA.portable.IDLEntity
17 // Этот комментарий появляется в объявлениях типов
18 public boolean boolValue = false;
19 public char charValue = ( char ) 0;
20 public char wCharValue = ( char ) 0;
21 public byte octetValue = ( byte ) 0;
22 public String stringValue = null;
23 public String wStringValue = null;
24 public short shortValue = ( short ) 0;
25 public short uShortValue = ( short ) 0;
26 public int longValue = ( int ) 0;
27 public int uLongValue = ( int } 0;
28 public long longLongValue = ( long ) 0;
29 public long uLongLongValue = ( long } 0;
30 public float floatValue = ( float } 0;
31 public double doubleValue = { double } 0;
32
33 public StructHap ()
34 (
35 ) // ctor
36
37 public StructHap( boolean _boolValue, char _charValue,
38 char _wCharValue, byte _octetValue, String _stringValue,
39 String wStringValue, short _shortValue,
40 short _uShortValue, int _longValue, int _uLongValue,
41 long _longLongValue, long _uLongLongValue,
42 float __f loatvalue, double _doubleValue )
43 (
44 boolValue = boolValue;
CORBA. Часть 1
339
45 eharValue — _charValue;
46 ttCharValne = _wCharValue;
47 octetvalua = _octetValue;
48 tringvalue = _atringValue;
49 vStringValue = _wStringValue;
50 shortValue = _ahortValue;
51 uShortValue ="~_u ShortValue;
52 longValue = _longValue;
53 uLongValue =■ _uLongValue;
54 longLongValue = _longLongV«lue;
55 uiongLongValue = _uLoogLongValu« ;
56 floatValue я _floatValue;
57 doubleValue = ^doubleValue;
58 ) // ctor
59
60 ) // класс StructMap
Рис 7.11. Файл StructMap.java. сгенерированный IDL-компил втором (для улучшения
восприятия переформатирован)
Переменные экземпляров, объявленные в строках 18-31 на рис. 7,11 —это
объявленные структурные переменные из рис. 7.10. Java-код инициализирует
переменные экземпляров, чтобы включить механизм явного приведения. Класс Java,
преобразованный из структуры, имеет в своем распоряжении два конструктора
для создания экземпляров объектов: конструктор без параметров и конструктор со
всеми типами полей данной структуры в качестве параметров. Создаваемые
объекты этого типа могут иметь значения по умолчанию (используя конструктор без
параметров) или предопределенные значения (используя конструктор второго типа).
Файл InterfaceNameOperations.java (рис. 7.12) содержит информацию об
интерфейсе, объявленном не рис. 7.10 в строках 40-52. ШЬ-компилятор
преобразовал операторы в Java, кроме того комментарии из IDL-файла перенесены в
сгенерированный код в строки 12, 15, 13 и 22. Интерфейс Java не может содержать ни
переменных экземпляров, ни кода реализации, что делает interface прекрасным
выбором для объявления видимой части структуры реализуемого объекта.
Заметим, что в InterfaceNameOperations.java не объявляются атрибуты, определенные
на рис. 7.10 в строках 43-44, разве что косвенно, через аксессор и мутатор {строки
16, 19 и 20). IDL не диктует способ реализации атрибутов, а требует, чтобы они
были доступны через конкретный интерфейс.
1 package жарteat;
2
3
4 /**
5* *aptest/InterfaceHameOperatione.Java
6* Generated by the IDL-to-Java compiler (portable), version "3.0"
7 * from Maptest.idl-
8 * Monday, Hay 14, 2001 4:18:09 PM PUT
9*/
10
11
12 // объявление интерфейса InterfaceHame
13 public interface XnterfaceNameOperations
14 (
15 // кошммтарий иерея атрибутом readwrite
16 int anAttribute О ;
340
Глава 7
17
18 // комментарий перед атрибутом readwrita
19 void anAttribute(int newAnAttribute);
20 int roAttributeO ,-
21
22 // комментарий перед объявлением методоя
23 void seqMetbod ( eaptest .StructMap[] aeq };
24 void boundSeqMethod( maptest. StructMapH saq );
25 void arrayMetnod( int[) array );
26 void intOutMetnod( org.omg.CORBA.intRolder intvalue };
27 ) // интерфейс InterfaceHarosOperations
Рис. 7.12. Файл InterfaceNameOperations.java, сгенерированный IDL (для улучшения
восприятия переформатирован)
Последний сгенерированный файл, обсуждаемый здесь, Int erf асе Name. Java,
представлен на рис. 7.13. Объявление интерфейса Interface Name расширяет три
интерфейса н объявляет константу constat! tValne в строке 14 (во всем остальном
этот интерфейс пуст). Константы всегда появляются в файле, названном в
соответствии с именем интерфейса (interface), который объявлен в IDL-файле.
1 package eaptest;
2
3 /••
4 * siaptest/InterfасеЫаяш. Java
5 * Generated by the IDL-to-Java compiler (portable), version "3-0"
6 * from eaptest.idl
7 * Monday, May 14, 2001 4.-18:09 PH FDT
8 */
9
10 // объявление интерфейса InterfaсеКале
11 public interface InterfaceName extends InterfaceNamaOperatione,
12 org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity
13 (
14 public static final int constantValue = < int ) ( 42 );
15 ) // интерфейс InterfaceHame
Рис. 7,13, Файл InterfaceNameOperations.java, сгенерированный IDL (для улучшения
восприятия переформатирован}
Когда IDL-компилятор обрабатывает IDL-файл, его самая важная задача —
создать два класса-посредника (один для сервера и один для клиента). Эти
посредники не содержат никаких прикладных функций — они решают только задачу
соединения клиента и сервера. Заглушка, которая непосредственно отображает API
сервера, объявленноз в IDL-описании, может включать любые открытые методы
сервера, вызываемые с помощью этой заглушки. В примере SystemCfock, когда
клиент вызывает метод cnrrentTimeMilfis, он вызывает метод заглушки, который
в свою очередь вызывает метод _invoke в абстрактном родительском классе этой
заглушки (org. omg.CORBA. port able. Object Imp!). Метод __invoke класса Object-
Imp 1 осуществляет маршалинг всех входных значений (в данном случае их нет),
а код заглушки обеспечивает демаршалинг всех возвращаемых значений (в данном
случае это значение типа long). С точки зрения клиента, он вызывает метод
сервера и приостанавливает свою работу до тех пор, пока вызванный метод не вернет
значение. На самом деле, брокер объектных запросов делает запрос, вызывая
метод _invoke заглушки для обработки вызова данного метода. Метод _invoke вызы-
CORBA. Часть 1 341
вает соответствующий метод серванта, ждет завершения метода и выполняет де-
маршалинг всех возвращенных значений.
До появления CORBA 3 существовало два типа статических вызовов: синхронные
(synchronous) и односторонние (oneway) (был еще один тип динамического вызова,
но он появился позже). Когда клиент осуществляет синхронный вызов метода
сервера, клиент блокируется (приостанавливает свою работу) пока не завершится
выполнение метода на сервере. Модификатор oneway объявлял вызываемый метод
как завершающий свою работу немедленно, но не задавал никаких опций
качества услуг (quality-oj'service). Quality of Service (известное как QoS) определяет
политику, установленную для конкретной задачи, предоставляющую этой задаче
возможность завершиться в разумный промежуток времени1. Консорциум OMG
придает QoS такое большое значение, что существует отдельная спецификация,
посвященная структуре QoS, предоставляющая разработчикам возможность
управлять QoS на разных уровнях (вообще говоря, это уровень брокера объектных
запросов, уровень потока и уровень объектных ссылок). Уровень объектных
ссылок QoS имеет приоритет над уровнем потока QoS, а уровень потока QoS — над
уровнем брокера объектных запросов2. QoS делает варианты вызовов в CORBA 3
более симметричными: они могут быть синхронными (normal нлн oneway) или
асинхронными [callback или polling). Обратный вызов (callback) — это вызов со
стороны сервера, тогда как опрос (polling) — это вызов со стороны клиента. В любом
случае, метод вызывается, исходя из потребностей вызывающего объекта. С точки
зрения реализации, обратный вызов влечет больше накладных расходов, так как
объект обратного вызова (клиент) должен использовать библиотеки CORBA, чтобы
функционировать в качестве CORBA-сервера. У опросного клиента
дополнительные издержки на повторные вызовы сервера отсутствуют. Это отличие касается
выполненного условия (сервер предупреждает объект обратного вызова) или
ожидаемого условия (клиент опрашивает объект с целью проверки изменений состоя-
Ш Совет по переносимости программ 7.2
Пока OMG еще не определился по поводу стандартных значений,
используемых по умолчанию для QoS, производители ORB могут задавать
несовместимые значения. Если вы используете эти значения, размещайте их
в отдельных файлах свойств, чтобы система считывала их при запуске.
Синхронный вызов является стандартным вызовом методов — клиент вызывает
метод и приостанавливает выполнение до завершения вызова. Если сигнатура
метода в IDL-описании включает ключевое слово oneway, компилятор создает код,
который не приостанавливает свою работу при вызове и осуществляет возврат,
исходя из установок QoS. Для немедленного возврата метод должен принимать
только параметры типа in, не должен возвращать значений и не может возбуждать
исключений. Вызывающей стороне не нужно ждать ответа (она уведомляет сервер
о том, что что-то произошло и ее не волнует, что будет происходить дальше) или
клиенту нужно как можно быстрее освободиться для доступа со стороны других
вызывающих объектов. В любом случае синхронный вызов инициирует клиент.
Асинхронный вызов отличается от синхронного. В ситуации обратного вызова
сервер может вызвать клиент в любой момент. Во многих случаях асинхронный
вызов клиента сервером прекрасно работает (например, слушатели в модели
событий Java), но как быть, если клиенту нужно в большей степени контролировать
1 Siege], Ph.D. Jon, CORBA 3, Second Edition, New York, NY: Wi]ey Computer Publishing,
2000, page 164.
2 Object Management Group, *The Common Object Request Broker: Architecture and
Specification., www.omg.org/egi-bin/doc7formal/01-02-33, October 2000, pages 4-41.
342 Глава 7
процесс уведомления сервером? Спецификация CORBA Asynchronous Method
Invocation (асинхронный вызов методов) поддерживает обе модели: и обратный вызов,
и опрос. Модель обратного вызова поддерживает (с различными опциями QoS)
способность серванта произвольно вызывать клиент по своему усмотрению. В модели
опроса клиент принимает решение о получении возможно существующих
результатов на основе обращения к oneway-методу. При стандартном опросе может
оказаться, что получать нечего, поэтому клиент продолжает опрос пока не получит
конкретней значение или пока не примет решение прекратить опрос.
В соответствии с существующим соглашением передача объектной ссылки
другому распределенному объекту (особенно, чтобы выразить намерение
осуществлять обратные вызовы) требует, чтобы его метод регистрации был объявлен как
oneway. Ключевое слово oneway указывает IDL-компнлятору, что следует создать
код, который не блокируется при вызове в ожидании завершения вызванной
операции. Неблокируемый стиль функционирования не позволяет методу
регистрации вызывать методы по входной объектной ссылке. Бели ни один из объектов не
поддерживает многопоточность, вызов сервером клиента может привести к
тупиковой ситуации. В том случае, если не используется ключевоз слово oneway,
клиент будет ждать ответа от сервера, а сервер будет ждать завершения работы
вызванного метода клиента (рис. ?.14). В разделе 7.6 представлен в явном виде
пример использования ключевого слова oneway.
„Ф Хороший стиль программирования 7.4
£ у} Чтобы избежать тупиковых ситуаций, всегда используйте ключевое
слово oneway при передаче объектной ссылки клиенту с помощью метода сер
вера.
1 r*gi*t«rClieiit( tbi* }
Клиент Сервер
2. cleot.endHeeaege()
Клиент и сервер в туп и козой ситуации.
Рис 7.14. Тупиковая ситуация созданная клиентом, вызывающим сервер, который
в свою очередь вызывает этого клиента
7.6. Пример AlarmClock
Приложения в примере AlannClock отличается от SystemClock. SyetemClock —
зто типичное приложение pull-модели — клиент принимает решение о том, когда
получать информацию с сервера. С другой стороны, пример AlannClock — это
типичное приложение push-модели — сервер принимает решение о том, когда
посылать информацию клиенту. В AlarmClock клиент устанавливает «сигнализацию*
на стороне сервера и ждет, когда сигнализация на сервере сработает. В данном
примере клиент генерирует случайное значение времени ожидания, на которое
устанавливается таймер сигнализации. Когда установленное время истекает, сервер
CORBA. Часть 1 343
уведомляет об этом клиента, и выведенный нз состояния ожидания клиент
отображает новоз время ожидания и вновь устанавливает таймер сигнализации.
7.6.1. AlarmClock.idl
На рис. 7.15 представлено IDL-описание двух серверов.
1 // Рис. 7.15. alaxmclockl.idl
2 // IDL-файя для примера AlarmClock
3
4 module alarm {
5 interface AlarmListener (
6 void updateTlm* ( in long long newTime ) ,-
7 >;
a
9 interface AlarmClock (
10 const string NAME = "AlarmClock";
11
12 oneway void addAlaxmListener ( in string listenerHaae,
13 in AlaraListener listener };
14
15 void мШиш( in string liatenerHama,
16 in long long seconds );
17 J;
is );
Рис. 7.15. alarmclockLidl
В строке 4 объявляется имя модуля, а в строках 5 и 9 объявляются имена типов
серверов. В модуле два объявления интерфейсов: серверный интерфейс и
интерфейс обратного вызова. Основной сервер — AlarmClock, a AlarmListener — это
определение обратного вызова. В строке 10 с использованием ключевого слова const
определена константа с именем NAME для кода, реализующего подключение
к сервису именования. Метод addAlannListener сервера AlarmClock в строках
12-13 добавляет объекты AlarmListener к списку объектов обратного вызова —
любых объектов, хранящих ссылку на AlarmListener, который может вызвать
метод npdateTime. Вызывающая сторона передает в качестве входного параметра
произвольное имя для сервера, чтобы связать свою объектную ссылку с этим
именем как с первичным ключом. Объект AlarmListener — это ссылка на слушатель,
поэтому мы добавляем ключевое слово oneway, чтобы гарантировать, что
компилятор создаст для этой операции неблокирующий код. Объект, реализующий
интерфейс AlarmListener, может зарегистрироваться в AlarmClock и затем ждать
истечения периода сигнализации. На рис, 7.16 представлена реализация такого
сервера.
7.6.2 AlarmClocklmpl.Java
На рис. 7.16 строки 21-52 те же, что и строки 26-59 на рис. 7.4 (метод register
в SystemClocklmpl). В строках 26-29 проверяются входные параметры. В строке
32 создается брокер объектных запросов (ORB) с помощью фабричного метода
ORB.init, а в строке 35 осуществляется подключение к брокеру объектных
запросов. В строках 38-41 осуществляется поиск ссылки на сервис именования. В
строке 45-47 создается объект NameComponent, а в строке 50 этот объект передается
сервису именования с помощью метода rebind.
1 // Рис. 7.16. AlarmClocklmpl.java
2 // Реализация сервера Alaгдеlock
3
4 package com.deitel.advjhtpl.idl.alarm,-
5
6 // Базовые оамт Java
7 import java.util.*;
8
9 // Пакеты расширений Java
10 import org.omg.CORBA.ORB;
11 import org omg CosHaming.*:
12 import org.omg.CosNaming.NamingContextPackage.*;
13
14 public class AlarmClocklmpl extends _AlarmClockImplBase {
15
16 // список содержи» пары name/alarm (имя/сигнах)
17 // зарегистрированных объехтов, ждущих сигнала
15 private Hashtable alarmList = new Haehtablet);
19
20 // регистрирует объект AlarmClockInpl а сервисе именования
21 public void register( String corbaNama, String params[] }
22 throws org.omg.CORBX.ORBPackage.InvalidMame,
23 org.oifrg.CosHaming.NamingContextPackage.XnvalidNama,
24 CannotProceed, NotPound
(
if ( ( corbaNama = null ) ||
( corbaNama.trim () .length() = 0 ) }
throw new IllegalArgumentException(
"Registration name cannot be null or blank");
// создаем и инициализируем брохер объектных запросов
ORB orb = ORB.init( parame, null );
// регистрируем объект this в ORB
orb.connect( this };
II получаем ссылку на сервис именовании
org.omg.CORBA.Object corbaObject =
orb.resolve_initial_referencas( "NameService" );
NamingContext naming =
NamingContextHelper.narrow( corbaObject );
// создаем месив NameComponent с информацией о пути
// для поиска данного объекта
NameComponant namingComponent =
new HamaComponent( corbaNama, "" };
NameComponant path[) = { namingComponent },-
// подключаем объех* AlarmClocklmpl к брокеру
// объектных запросов
naming.rebind( path, this };
System.out.println( "Rebind complete" );
// метод, используемый клиентами, которые хотят зарегнетриров!
// объекты обратного вызова/слушатели
public void addAlarmListener( String listenerName,
CORBA. Часть 1
AlarmLiatener listener )
throws DuplicateNameException
if ( listenerName = null I I
listenerName.trim().lengthf) = 0 )
throw naw lllegalArgumentException(
"Name cannot be null or blank");
else
if ( alarmList.get( listenerName ) ■= null )
throw new IllegalArgumentException(
"Haste is already registered, please choose another" );
if ( listener = null )
throw new lllagalArgumentException(
"Listener cannot be null" );
// создаем новый Timer и сохранней его под именам слушателя
alarmList.put( listenerName, naw AlarmTuoer( listener ) ) ,-
// Устанавливает сигнал для клиента. Если i
// варегистрироаах возбуждается исключение
public void setAlarm( String name, long seconds )
{
// устанавливаем сигнах для конкретного клиента
AlannTimer timer = { AlannTimer ) alarmList.get( name );
if ( tuner = null )
throw new lllegalArgumentException(
"No clock found for the incoming name" );
timer.schedule{ new TaskWrapper(timer.getListaner()>
seconds), seconds * 1000 );
94 // метод main, реализующий сервер AlarmClock
95 public static void main{ String args[] ) throws Exceptioi
96 (
97 AlarmClockImp1 alarm = new AlarmClocklmpl();
98 alarm.register( AlarmClock.NAME, arge );
99
100 Java.lang.Object object » new java.lang.Object();
101
102 // фиксируем сервер в рабочем режиме
103 synchronized( object ) (
104 object.wait();
105 )
106 )
107
108 // Каждый слушатель получает назначенный ему AlannTimer
109 private class AlarmTuner extends Timer (
110
111 // Слушатель, которому назначен даяний Timer
112 private AlarmListaner listener;
113
114 public AlaxittTimer ( AlarmLietener 1 }
115 [
116 listener = 1;
117 >
118
119 // Метод доступ*, чтобы ин моган добраться )
120 // обчектнся ссылки на слушатель
121 public AlaraListanar getListener(}
122 (
123 return listener;
124 }
125 ) // !
126
127 // TaskHrapper озтнечает sa i
128 // когда истекает их время смгн&лиааамм
129 private class TaskHrapper extends TiraerTaek [
130
131 // Ссылка ка наш слушатель
132 private AlamListanax listener;
133 private long seconds;
134
135 // TaskHrapper должен аяштъ, кого аымтать и
136 // когда была установлена сигнализация (в секундах)
137 public TaskHrapper( XlarmListaner 1, long s }
133 1
139 listener = 1;
140 seconds = s;
141 }
142
143 public void run О
144 (
145 // Раябудк-ка юс!
146 listener.updataTime(seconds);
147
148 // Завершим мот TaskHrapper. Когда клиенту будет
149 // нужен яовкй сигнал, ин создадим новый TaskHrapper
150 this.cancel(};
151 >
152 } // вавержеиме акутреннего класса TaskHrapper
153 ) // аавериание класса AlarmClocklapl
Рис. 7.16. AlarmClocklmpI - реализация сервера AlarmClock
В строках 56-77 объявлен метод регнстрации addAlarmLiBtener. После
проверки стандартных параметров в строке 76 имя слушателя сохраняется в Hash table,
если оно не было сохранено. Hash table содержит имя слушателя и объект Alarm-
Timer, который находится в состоянии ожидания заданное число секунд, затем
уведомляет слушатель, что сигнал был подан (AlarmTiroer наследует от ja-
va.ntiLTiraer, который организует поточную обработку для вызова клиента
независимо от основного потока управления). Внутренний класс Task Wrapper
(зарегистрированный у AlarmTimer в строках 90 и 91) вызывает метод слушателя
updateTiroe в методе TaakWrapper.ron (строки 143-151) и сразу заканчивается
при появлении сигнала. Каждый раз, когда клиент устанавливает сигнализацию
с помощью метода eetAlarm (строки 81-92), мы создаем новый TaekWrapper
и возвращаем его вызывающей стороне насколько возможно быстро.
COR В А. Часть 1 347
7.6.3. AlarmClockClient.java
На рис. 7.17 представлен графический пользовательский интерфейс
приложения. Он состоит из компонента JFrame, который отображает строку, информируя
пользователя о появлении сигнала.
1 // Рис. 7.17. ClockClientGOT.java
2 // Пользовательский интерфейс клиента AiarmClockClient
3
4 package сея.deitel.advjhtpl.idl.alarm;
5
6 // Базовые пакета Java
7 mport java.awt.*;
8 mport Java.avt.event.*;
9
ID // Пакет расширений Java
11 import javax.swing.*;
12
13 public class ClockClientGOI extends JFrame [
14 private JLabel outputLabel;
15
16 // создает пользовательский интерфейс
17 public ClockClientGUT-O
18 {
19 super( "Clock GOT." J;
20
21 outputLabel =
22 new JLabel( "The alarm has not gone off..." };
23 getContentPana(J.add( outputLabel, BorderLayout.NORTH J;
24
25 setDefaultCloseOperatiort( JFrame.EXIT_ON_CLOSE );
26 8etReaizabl«( false };
27 Dimension screenSize =
28 Toolkit.getDefaultToolkit().getScreenSize();
29 setSize( new Dimension( 450, 100 } };
30 setLocation( ( screenSize.width - 450 ) / 2,
31 { screenSize.height - 100 ) / 2 );
32 J
33
34 // задаем текст метки
35 public void aetText( String Hessage )
36 {
37 outputLabel.aetT*xt( message J;
38 J
39
40 ) // завершение класса ClockClientGOI
Рис. 7.17. ClockClientGUI уведомляет пользователя о появлении сигнала
AiarmClockClient (рис. 7.18) — клиент для сервиса AlarmClock. В
строках 22-38 определен конструктор AiarmClockClient. Используя системное время,
AiarmClockClient сам себе дает имя и пссле создания пользовательского
интерфейса передает это имя методу connectToAJarmServer (строки 41-68) вместе со
всеми входными параметрами. AiarmClockClient подключается к AlannClock тем
же способом, которым SyetemClockClient подключается к TimeServer. В строке 43
создается объект брокера объектных запросов, используя фабричный метод броке-
348
Глава 7
pa объектных запросов init. Предавая клиенту ссылку во втором входном
параметре, AlarmCiockCUeat регистрируется у брокера объектных запросов. Вновь
созданный экземпляр брокера объектных запросов яаляется посредником всех вызовов
от клиента и к нему. Брокер объектных запросов, созданный как объект,
изолированный от AlarmClockClient, может управлять доступом к AlarmClockClient и от
него, но только если у брокера имеется действующая ссылка на полностью
оформленные вызовы, начинающиеся (или заканчивающиеся) в коде клиентской
заглушки. Метод init делает AlarmClockClient совместимым брокером объектных
запросов. Используя тот же брокер объектных запросов, AlarmClockClient может
получить ссылку на сервис именования. Как и в примерена рис. 7.5, брокеры объ-_
ектных запросов имеют минисервисы именования, которые позволяют им начи
нать работу с предопределенным списком внешних сервисов (сервис именования
один из них). С помощью метода NamingContextHelper.narrow в строке 55
осуществляется приведение COR В А-объекта, возвращенного resolve_initia!_references,
к объекту типа NamingContext. Программа на рис. 7.18 отображает выходные
данные при соединении клиента и сервера.
Объекты NameComponent дают клиентам возможность просматривать списки
сервиса именования с целью поиска нужных сервисов. В строках 58-59 создается
объект NameComponent, который использует сервис именования для поиска
сервиса AlarmClock. После того как AlarmClockClient вызовет метод сервиса
именования resolve и приведет возвращенную объектную ссылку к типу AlarmClock
(строки 61-62), AlarmClockClient получает возможность выполнить удаленный
вызов метода. В строке 65 клиент регистрируется в AlarmClock в качестве
AlarmListener, превращая его в объект асинхронного обратного вызова.
AlarmClockClient завершает метод connect To AlarmServer после отображения
пользовательского интерфейса и установки сигнализации с помощью метода updateTimc
(строки 71-80) — этот метод (объявленный в IDL-интерфейсе AlarmListener)
возвращает сигнализацию в исходное состояние после каждого вызова от сервера,
давая возможность клиенту находиться а режиме ожидания до истечения
установленного срока. Основное отличие этого кода от кода SystemCIockClient — поведение
клиента после того, как найден сервис именования. Метод AlarmClock Helper,
narrow является единственным фрагментом кода, не используемым повторно.
1 // Рис. 7.18. AlarmClockClient.Java
2 // Клиент сервиса AlarmClock
3
4 package com.deitel.advjhtpl.idl.alarm;
5
6 // Пакеты OMG CORBA
7 import org.omg.CORBA.ORB;
8 import org.omg.CosNaming.*;
9 import org.omg.CosNaming.NamingContextFackage.*;
10
11 public class AlarmClockClient extends _AlarmListenerImplBase {
12
13 // Ссылка м отображаемый пользовательский интерфейс
14 private ClockClientGUI gui;
15
16 // Ссылка ка селер с сигнализацией, к которому мы подключаемся
17 private AlarmClock alarmClock;
18 // Имя клиента, используемое сервером для выполнения
19 // обратных вызовов
20 private String name;
21
CORBA. Часть 1
public AlarmClockClient( String parents [] ) throws Exception
(
// создаем отображаемое имя, уникальное для
// виртуальная машин, работающих на Фом же компьютере
name = new Long[
System.currentTimeMillisO % 10000 ).toStringO;
// создаем пользовательский интерфейс для отображения
// имени и секунд до сигнал*
gui = new ClockClientGUI();
// подключаемся к сервису TimeService
connectToAlarmServer( name, регате );
// отображаем пользовательский интерфейс и ждем,
// когда пользователь завершит работу приложения
gui.show();
}
// выполняем подключение к AlarmServer
private void connectToAlarmServer(
String name, String parame()-)
throws org.omg.CORBA.ORBPackage.InvalidName,
org.omg.CosNaming.NanutngConfcextPackage.InvalidName,
NotFound, CannotFroceed
(
// подключаем AlarmClockClient к ORB
ORB orb = ORB.initf params, null );
// подключаемся к сервису именования м ищем объектную
// ссылку же сервис AlarmClock
org.omg.CORBA.Object corbaObject =
orb.resolve_initial_references( "NameService" );
NamingContext naming =»
NamingContextHelper.narrow( corbaObject );
// по объектной ссылке определяем имя
NameComponent nameComponent =
new NameComponent( AlarmClock.NAME, "" );
NameComponent path() = ( nameComponent );
alarmClock =
AlarmClоckHelper.narrow( naming.resolve{ path ) );
// регистрируем объект в сервисе AlarmClock
alarmClock.addAlarmListener( name, this ) ,-
gui.show();
updateTime[ 0 ) ;
)
// метод обратного вызова, определенный в AlarmLiatener
public void updateTime( long seconds )
t
// Готовим значение времени для устажовки сигнализации
int newTime = ( int )( Math.random() * 10.0 ) + 1;
gui.eetText( "Alarm " + name + " came in at " + seconds ■
" seconds. Resetting to " + newTime + " seconds" );
Э50
Глава 7
77 alarjoCloek.«etAlarm( name, nawTuue );
78 )
79
80 // катод amin, раалхауящмй клиента
81 public static void main( String ergs[] ) throw* Exception
82 (
S3 // создаем клиент
84 try (
85 AlarraClockClient client «• new AlarmClockClient( args );
86 )
87 // обраба«амем кешшчеимя, аоакикашцие в процессе работ
89 catch ( Exception exception ) (
90 Systen.out.println(
91 "Exception thrown by AlarmClockClient:" );
92 exception.printStackTrace();
93 )
94 )
95
96 ) // завершение киасса AlaraClockClient
Рис. 7.18. AlarmClockClient является клиентом AlarmClock
7.7. Распределенные исключения
В объектно-ориентированном окружении исключения являются
предпочтительным средством обработки ситуаций, связанных с возникновением ошибок.
Механизм обработки исключений в Java работает на двух уровнях: во время
компиляции и во время выполнения. Разработчики могут встраивать обработку
исключений во время компиляции, используя блоки try или расширяя сигнатуры
методов для включения всех возбуждаемых ими исключений. Если отдельное
исключение носит такой общий характер, что код пишется исключительно с целью
обеспечить восстановление после определенной ошибки, такое исключение может
наследовать от RuntimeException, затем разработчик определяет в явном виде,
в каком случае это исключение должно быть обработано. Во время выполнения
этот код уже настроен на обработку стандартных исключительных ситуаций,
и любой код, который может вызвать RuntimeException должен быть тщательно
исследован до вызова, чтобы снизить риск аварийного завершения. Мы должны
сами анализировать события, которые возникают, когда Java-программа
возбуждает исключительные ситуации в сетевой среде. В сервер CORBA, с которым осу-
CORBA. Часть 1
351
ществляется взаимодействие, может быть написан на каком-то другом языке
программирования. Может ли это привести к необходимости использованию
устаревших методов проверки кодов состояний?
В CORBA определены два типа исключений: системные и пользовательские.
Системные исключения введены для использования инфраструктурой CORBA,
и все операции определены на IDL. Пользовательские исключения являются
исключениями CORBA, онисанными на IDL разработчиками CORBA-Системы.
Любой вызов распределенного объекта может привести к исключительной ситуации.
Любое действие с CORBA-объектом может вызвать исключительную ситуацию
SystemException CORBA, для обработки которой разработчики пишут
соответствующий код. Однако иногда требуют внимания и исключительные ситуации
времени выполнения, поэтому обдумайте использование дополнительных блоков try
или классов-оберток заглушки распределенного объекта так, чтобы клиенту не
нужно было знать, что существуют случаи дополнительной обработки исключений
при возникновении исключительных ситуаций.
Разработчики могут найти исключительные ситуации, возбуждаемые CORBA-
объектом, заглянув в связанный с данным объектом IDL-файл. Например, сервис
именования объявляет метод rebind в CosNaming.idl следующим образом:1
void rebind( in Name n, in Object obj )
raises( NotFound, CannotProceed,
XnvalidName );
Ключевое слово raises связано с ключевым словом Java throws. Метод rebhid
возбуждает три исключительные ситуации: NotFound, CannotProceed и Invalid-
Name. Cos Naming Jdl определяет их следующим образом2:
enum NotFoundReason {
mi s aing_node,
not_context,
not_6bjeot
exception NotFound (
NotFoundReason why;
Name reat_of_name;
}:
exception CannotProceed {
NamingContext cxt;
Name rest_of_name;
);
exception InvalidHawe();
Обратите внимание иа использование ключевого слова exception. Исключения
ШЬ связаны с исключениями Java путем косвенного наследования от
java.lang.Except ion (непосредственно они наследуют от 11вегException). Исключение Invalid-
Name — подкласс Java.lang.Exception — ближе к обычной практике Java, но
только как средство спецификации конкретной проблемной области. Исключение
CannotProceed определяет дополнительные атрибуты, доступные стороне,
принимающей объект исключения, но все эти атрибуты закрытые.
1 CORBAservices: Cammon Object Services Specification, OMG, page 3-7. Updated December
1998. (Сервисы CORBA: спецификация общих объектных сервисов)
2 CORBAservices: Common Object Services Specification, OMG, page 3-7. Updated December
1998. {Сервисы CORBA: спецификация общих объектных сервисов)
352
Глава 7
По определению стандартные исключения CORBA преобразуются в
исключения Java в виде final классов. Если исключение CORBA наследует от System-
Exception, оно наследует также и от RuntimeException. Все исключения CORBA
доступны для использования разработчиками в различных пакетах org.omg,
поставляемых вместе с JDK. Используйте исключения CORBA в коде сервера, если
это нужно, возбуждайте исключения CORBA только при возникновении проблем,
связанных с CORBA. Определяйте дополнительные распределенные исключения
для ситуаций, относящихся к конкретному сервису, которые требуют реакции
удаленной вызывающей стороны.
Определение дополнительных исключений на IDL осуществляется просто. На
рис. 7.19 в строках 2-4 объявляется исключение, полезное для оповещения
вызывающей стороны о том, что при попытке извлечения записи о клиенте произошла
ошибка. Операция find (строка 7) объявляет о своем, намерении возбудить Data-
baseException с помощью ключевого слова raises.
1 module domain (
2 exception DatabaseException (
3 string msg;
4 };
5
6 interface CuetomerHome {
7 void find( in long key ) raises ( Databaa«Exception );
8 );
9 1;
Рис. 7.19. Пользовательское исключение CORBA (DatabaseException) и операция,
способная возбудить это исключение
IDL-компилятор ^нерирует определение класса исключения и не требует
последующего участия разработчика. Сгенерированный класс DatabaseException
показан на рис. 7.20.
1 public final class DatabaseException
2 extends org.omg.CORBA.DserException
3 implements org.omg.CORBA.portable.IDLEntity
i i
5 public String msg = null;
6
7 public DuplicateNameException ()
8 {
9 ) // ctor
10
11 public DuplicateNameException( String _msg )
12 <
13 msg = _msg;
14 ) // ctor
15 ) // класс DatabaseException
Рис. 7.20. Сгенерированный файл Data base Except ion.java (для улучшений восприятия
произведено дополнительное форматирование)
Чтобы сделать AlarmClock более дружественным по отношению к клиенту, мы
можем добавить к методу addAlarmListener {строки 16-18) на рис. 7.21
пользовательское исключение DuplicateNameException. Добавление этого исключения
к сигнатуре addAlarmListener делает ненужным ключевое слово oneway, которое
CORBA. Часть 1
в out или inout или
1 // Рис. 7.21. alarmelock2.idl
2 // IDL-файл для примера AlarmCloclt
3
4 module alarm (
5 exception DuplicateNameException
6 string mag;
nterf&ca Alarmliiatener (
void updateTiate( in long long nevTii
interface AlarmClock (
const string NAME = "AlareClock";
void addAlarmListener( in string listenerHame,
in Alazmliiatener listener )
raises( DuplicateNameException );
void setAlaxn( in string listenerHame,
in long long seconds );
Рис. 7.21. lDL-файл aiarmdock2Jdl для примера AlarmClock
С добавлением данного исключения к методу addAlarmListener клиенты не
смогут зарегистрироваться для установки сигнализации, если у них одинаковые
имена. Это должно быть предваритаяьным условием для данного метода в любом
случае. Единственные изменения, которые нам нужно внести в метод
addAlarmListener {рис. 7.16) показаны на рис. 7.22 (строки 57 и 66). В строке 57
добавляется выражение throws DuplicateNameException для согласования с объявлением
этого метода в AlarmClockOperatione.java (интерфейс, сгенерированный при
компиляции alarmclock2.idl). В строке 65 выполняется проверка повторения имени, а
в строке 66 возбуждается рассматриваемоз исключение, если существует
слушатель, зарегистрированный с уже используемым именем.
1 public void addAlarmListener( String listenerHame,
2 AlarmListener listener ) throws DuplicateNameException
3 t
4 if ( listenerHame = null I I
5 listenerHame.trim().length!) = 0 )
6 throw nev IllegalArgumentException(
7 "Name cannot be null or blank" ) ,-
8 else
9
10 if ( liat.get( liatenerHame ) != null )
11 throw пей DuplicateNameException(
12 "Name is already registered, please choose another" );
13 else
14
15 if ( listener = null )
16 throw new XllegalAzgumentException(
354
Глава 7
17 "Listener cannot be null" );
18
19 // Создаем новик Timer и сохраняем его с именем слушателя
20 alarmList.putt liatenerName, new AlannTlroer( listener ) );
21 )
Рис. 7.22. Фрагмент AlarmClocklmpl.java
Добавление DoplicateNameException к сигнатуре addAIarmListener оказывает
влияние на все методы, которые его вызывают. В данном случае метод connectTo-
AlarmServer (рис. 7.18) должен добавить DuplicateNameException к сигнатуре
своего метода. Осуществление этого изменения в сигнатуре connectToAlarmServer
требует от тех, кто его вызывает, или перехватывать это исключение, или
передавать его дальше тем, кто вызвал их {в данном примере все исключения
перехватываются методом main).
Расширение исключений Java на случай распределенных исключений не
означает, что все механизмы Java имеют такие же расширенные возможности. Одна из
областей, не затрагиваемых в COR В А, — это сборка мусора, освобождение
выделенной памяти. COR В А не участвует напрямую в выделении памяти объектам
и в ее освобождении. CORBA не предопределяет реализацию, поэтому мы будем
говорить о сборке мусора в контексте использования Java в среде CORBA. Другие
языки имеют свои механизмы выделения и освобождения памяти — эти
механизмы создают объекты для удаленного использования иди создают структуры для
другого процесса, существующего где-то «вовне*. Java не требует от
разработчиков следить за освобождением памяти. Те возможности, которые в первую очередь
привлекли миллионы разработчиков к Java, остаются доступными даже при
использовании в сочетании с традиционными языками, объединенными в одно целое
с помощью CORBA.
При создании экземпляра структуры в Java, как мы уже упоминали, все члены
созданного экземпляра являются открытыми, оставляя объект незащищенным.
Когда клиент посылает объект-структуру как параметр удаленной операции,
скелет далает его копию на стороне сервера для использования в качестве локального
объекта. Что происходит с исходным объектом данных у клиента? До тех пор пока
какая-либо переменная имеет дескриптор этой ссылки, память, выделенная под
соответствующий объект-структуру, не освобождается. Как только оказывается,
что ни одна переменная не ссылается н» данный объект (счетчик ссылок
обнуляется), память, выдаленная под этот объект, освобождается.
7.8. Практический пример. Приложение Chat
В главе 2 была предложена простая реализация примера RMI Messenger. В
данном примере RMI Messenger переводится на CORBA с использованием концепций,
которые были рассмотрены в примерах SystemClock и AlarmClock. Программа,
реализующая чат, является естественным приложением для технологий
распределенных систем, таких как RMI и CORBA. Чат — это одно из основных сетевых
приложений, использующих центральный ретрансляционный узел, на который
клиенты, участники чата, передают сообщения, а промежуточное ПО этого узла
сообщения распространяет.
Моделирование архитектуры CORBA Messenger начинается с определения
основной видимой пользователю функциональности, реализуемой данным
приложением. В UML описание функций, видимых пользователю, называется сценариями
использования (use cases). В CORBA Messenger существуют следующие сценарии
использов ани я:
CORBA. Часть 1 355
1. Соединение. Клиент находит и подключается к чат-серверу.
2. Отключение. Клиент завершает сзанс чата путем отключения от чат-сервера.
3. Отправка сообщения. Клиент, подключенный к серверу, создает
чат-сообщение и передает его чат-серверу.
4. Получение сообщения. Клиент, подключенный к серверу, получает
сообщения, передаваемые чат-сервером. Все клиенты, подключенные к
данному чат-серверу, получают одно и то же сообщение.
£ЧК Совет по тестированию и отладке 7.1
^у В процессе разработки распределенного объекта, где только можно,
создавайте объекты для тестирования. Регулярно тестируйте основные
возможности, предположительно реализуемые сервером.
Сервис именования является фундаментальным аспектом реализации CORBA-
системы. Именование — стандартный способ, которым клиенты находят оер-
внс(ы), необходимый им для выполнения своих задач. Имя чат-сервера CORBA
Messenger — "ChatServer". При запросе сервис именования возвращает ссылку
Interoperable Object Reference (IOR), связанную с именем данного чат-сервера (своз
имя чат-сервер передает сервису именования при первом запуске).
Еще одним способом для клиента найти сервис является использование
строковой версии IOR-ссылки искомого сервиса. Как мы упоминали ранее, объектная
ссылка содержит информацию, необходимую брокеру объектных запросов для
локализации распределенного объекта. Полученная от сервиса именования
объектная ссылка (в форме IOR) является действительной все время существования
удаленного объекта в активном состоянии через посредство конкретного брокера
объектных запросов. Строковая IOR действительна все время, пока сервер способен
обрабатывать любые обращения (это зависит от установок качества обслуживания
переносимого адаптера объекта (РОА) данного серванта). Если нужно, IOR можно
сохранить во внешнем файле для будущего использования.
Объектная ссылка на любой распределенный объект может быть возвращена
в виде строки для использования клиентами с помощью мзтода брокера объектных
запросов ob|ect_to_8tring, однако применение строковой IOR не дает гарантии,
что указанный объект доступен для использования. 10R гарантирует корректность
объектной ссылки в каждом сеансе, в котором данный объект является
действующим (доступен через соответствующий брокер объектных запросов по адресу,
указанному в объектной ссылке). Однако клиенты всегда полагают, что актуальный
путь к конкретному объекту имеется у сервиса именования, тогда как никаких
гарантий относительно действительности строковой IOR быть не может. Это похоже
на запуск автомобиля — умение запускать двигатель коротким замыканиам
проводов можно использовать во всех автомобилях, но короткое замыкание проводов
сложнее, чем завести автомобиль с помощью ключа зажигания.
Абстракции типа IOR предоставляют разработчикам больше возможностей при
разработке. На первый взгляд, передача сообщения чат-серверу в виде строки
выглядит достаточной, чтобы предоставить серверу возможность посылать
сообщения всем клиентам. Однако моделирование предметной области позволяет нам
расширить строковые данные в Message. Это дает нам больше возможностей для
работы со значениями, передаваемыми между клиентом я сервером (например,
передача исходящего имени чата вместе с самим сообщением). Создание Chat-
Message в виде структуры означает, что все подключенные клиенты получают
свою собственную копию данного сообщения для локвльной обработки вместо
того, чтобы совместно использовать один централизованный объект (строки 6-11
на рис. 7.23). Возможность одновременного доступ8 всех пользователей данной
системы к одному совместно используемому объекту ChatMessage может создать
356
Глава 7
узкое место в системе при попытке всех клиентов одновременно считать
содержимое этого объекта.
Использование структур в IDL представляет собой реальную проблему для
объектно-ориентированной реализации. Структура полезна для пересылки набора
данных через сеть, повышая производительность (пересылка 10 структур между
клиентом и сервером обеспечивает лучший режим работы, чем пересылка 10
объектных ссылок при доступе к отдельным членам). Однако при преобразовании
структуры в объект результат не становится полностью
объектно-ориентированным, если не решить дополнительные проблемы. В случае структур данные
перестают быть инкапсулированными. CORBA не зависит от языка
программирования, a IDL не является языком, определяющим реализацию. Как упоминалось
ранее, все конструкции в ШЬ являются открытыми, так как С, COBOL и другие
языки не поддерживают концепции инкапсуляция. Бели в роли промежуточного
ПО выступает сервер, написанный на С, включенным в его состав заглушкам
и скелетам должно быть известно, как преобравовать специфический Java-объект
в правильную структуру языка программирования С. Это отображение может
определяться кодом, сгенерированным с помощью ГОЬ-компилятора. Однако
создание более сложного кода поднимает иные проблемы, например, как попучить
доступ в область видимости объекта, чтобы сделать возможным маршалинг его заново
инкапсулированных ПО всем правилам значений. Помните, CORBA — это «клей*,
обеспечивающий возможность интеграции систем, а не объектно-ориентированное
средство решения прикладных задач.
Разработчикам не нужно ничего делать с тем кодом, который сгенерирован для
представления структур (им просто нужно быть внимательнее с использованием
инкапсулированных значений). Если ChatMeesage должен стать распределенным
объектом, то мы должны будем определить ChatMeesage в качестве интерфейса
IDL, реализовать который должны будут разработчики.
7.8.1. chat.idl
Серверу нужны интерфейсы, чтобы регистрировать и откреплять клиента,
а также и посылать всем клиентам сообщения. Клиент использует собственные
интерфейсы, чтобы принимать сообщения (после регистрации на сервере).
На рис. 7.23 определены два интерфейса для приложения Chat. Строки 29-44
отражают ШЬ-описание трех операций, предлагаемых в интерфейсе ChatServer.
Вложенные имена модулей client (строка 18) и server (строка 24) создают новые
области видимости, В строке 33 имеется указание на тип client::ChatClient для
использования с методом registerCUent (двойное двоеточие является способом
задания пространства имен ШЬ, также как точка служит для задания пространства
имен пакетов/классов Java). В строках 14-21 определен ChatClient и его сервис
доставки входящих сообщении пользователю. То, что серверу ChatServer известен
идентификатор ChatClient, определяет выбор идентификатора для обратного
вызова. В IDL нет указания, что клиенту ChatClient нужно знать что-то еще, кроме
идентификатора ChatMeesage.
1 // Chat.idl
2 // Файл содержит IDL-описаяив API сервера ChatServer,
3 // а также сяишм ChatClient и ChatMessage.
4
5 module corba (
6 struct ChatMessaga (
7
8 // Свойства CbatMessage
9 string from;
CORBA. Часть 1
357
10 string message;
11 );
12
13 module client (
14 interface ChatClient (
15
16 // получает новое сообщение
17 void deliverMessage( in ChatMessage message );
18
19 // метод, вызываемый ори завершении работы сервера
20 void serverStopping{);
21 );
22 );
23
24 module server (
25 interface StoppablaChatServer (
26 void stopServer();
27 );
28
29 interface ChatServer (
30 const string NAME = "ChatServer";
31
32 // регистрирует новый ChatClient ■ ChatServer
33 oneway void registerClient( in client::ChatClient client );
34
35 // отменяет регистрацию ChatClient a ChatServer
36 void unregisterClient( in client: : ChatClient client
J.ST
38 // отправляет новоа сообщение ChatServer
39 void poatMessage{ in ChatMessage message );
40 J;
41
42 // со*дает комбинированный: интерфейс
43 interface ChatService : ChatServer, StoppableChatServer (
44 );
45 };
46
47 ); // завершение модуля corba
Рис. 7.23. Описания интерфейсов ChatServer, ChatClient и ChatMessage
7.8.2. ChatServerlmpi.java
IDL-компилятор при компиляции chat.idl создает следующие файлы для
использования на стороне сервера:
• messenger\client\_ChatClientImplBase.java
• messen ger\server\_Cbat Serve rlmplBase. j ava
■ meseenger\client\Chat Client. Java
• m essenger\client\ChatC lient Operations, j a va
• messenger\Ch*tMeeeage.java
• messenger\server\Chat Server, java
• messenger \server\Chat Serve rOperations java
358
Глава 7
Дополнительно компилятор создает два файла классов, которые
непосредственно не используютсл:
* messenger\CbatMessageHelper.java
• messenger\ChatMessageHolder.java
Классы ImplBase используют Chat Message Helper для чтения и записи в/из
поток ввода/вывода для объектов, отправляющих сообщения. Разработчики никогда
не сталкиваются с таким использованием ChatMessageHelper, если только они не
заглянут в сгенерированные файлы. С другой стороны, разработчикам придется
использовать С bat Message Holder непосредственно, если они объявили Chat
Message как переменную out. (ChatMessage используется только как переменная in,
поэтому Chat Message Holder не используется.)
Заметим, что ChatCiient, ChatCIientOperations и ChatCiient ImplBase
одинаково обращаются к клиенту с точки зрения сервера Chat Server, ChatCiient — это
тоже сервер (который превращает Chat Server в клиента во время обращений
к ChatCiient). Мы вернемся к этим файлам при обсуждении реализации клиента.
Chat Server отвечает за ведение списка клиентов, которые хотят принимать
сообщения, распространяемые сервером. Для простоты мы используем Hash table
с именем пользователя в качестве ключа и объектную ссылку на ChatCiient в
качестве значения. Метод registerClient сохраняет имя пользователя и объектную
ссылку на ChatCiient в Hashtable, метод removeClient удаляет объектную ссылку,
используя имя пользователя в качестве ключа в Hashtable, и метод poatMessage
проходит по списку объектных ссылок и вызывает мэтод deliverMessage для
каждого зарегистрированного клиента.
На рис. 7.24 в строках 11-13 импортируются стандартные пакеты, которые
содержат символы для CORBA-классов. В строке 19 Cha t Server Imp 1 наследует от
_Chat Server ImplBase. Абстрактный класс ChatServerlmplBase реализует
interface Chat Server, который расширяет ChatServerOperations и требует, чтобы
Chat Serve rlmpl реализовал методы, определенные в интерфейсе
ChatServerOperations. Реализация этих трех методов проста и несущественно отличается от
исходной реализации ChatServerlmpl, представленной н главе 2 (которая использует
Vector вместо Hashtable). В строке 25 определяется HashMap для хранения
ссылок на зарегистрированных клиентов (создается также экземпляр Hash Map).
1 // ChatServerlmpl.Java
2 // ChatServerlmpl реализует CORBA ChatServer.
3 package com.deitel.messenger.corba.server;
4
5 // Базовые оахетн Java
6 import java.io.*;
7 import java.util.*;
8 import Java.net.MalformedUKLExoeption;
9
10 // Пакеты расширения Java
11 import org.omg.CosNaming.*;
12 import org.omg.CosHaming.tlamingContextPackage.*;
13 import org.oag.CORBA.*;
14
15 // Пакета Deitel
16 import con.deitel.messenger.corba.CbatMesaage;
17 import com.deitel.messenger.corba.client.ChatCiient;
18
19 public class ChatServerlmpl extends _ChatServerImplBase {
20
21 // ORB, который подключает нас к сети
CORBA. Часть 1 359
22 private ORB orb;
23
24 // Список ссылок клиентов ChatClient
25 private Кар clients » new HashMapO;
26
27 // создает новый ChatServerImp1
28 public ChatServerlap1( String[] args )
29 throws Exception
30 (
31 super();
32 register! ChatServer.NAME, args );
33 )
34
35 // регистрирует нового ChatClient у ChatServer
36 public void registerClient( ChatClient client )
37 (
38 // добавляем клиента к списку зарегистрированных клиентов
39 String key = orb.object_to_string( client );
40 synchronized! clients ) {
41 clients.put{ key, client );
42 )
43
44 System.out.println( "Registered Client: " + key );
45
46 ) // завершение метода registerClient
47
4B // отменяет регистрацию клиента у ChatServer
49 public void onregisterClient( ChatClient client )
50 {
51 // удаляем клиента из списка зарегистрированных клиентов
52 String key = orb.object_to_*tring( client );
53 synchronised( clients ) (
54 clients.remove{ key );
55 )
56
57 System.out.println( "Unregistered Client: " + key );
56
59 ) // завершение метода onregisterClient
60
61 // отправляет новое сообщение ChatServer
62 public void рое tMessage( Cha tMessage message )
63 (
64 Iterator iterator = null;
65
66 // полутаем итератор для множества зарегистрированных клиентов
67 synchronized( clients ) (
68 iterator = new HashSet( clients.entrySet(} }.iterator ();
69 >
70
71 // отправляем сообщение наклону ChatClient
72 while ( iterator.hasNext() ) (
73 ChatClient client =
74 ( ChatClient ) ( ( Hap.Entry ) iterator.next() ).getValue();
75 client.deliverHeaaage( message );
76 )
77
78 } // заверившие метода poatMeaaage
79
80 // Регистрируем объект ChatServerXmpl а сервисе именования
81 public void register! String serverName, String[] parameters )
82 throw» HotFound, CannotFroceed,
83 org.omg.CosNaming.HamingContextPackaga.invalidName,
84 org.ong.CORBA.ORBPackage.Invalidsam*
85 (
86 if ( serverHame = null }
87 throw new IllegalArgumentBxception(
88 "Registration name can not be null" );
89
90 // Связываем объект CnatServerlmpl с сервисом именования,
91 // создаем и инициализируем ORB
92 orb - ORB.init( parameters, null );
93
94 // соадаем сервант и регистрируем еко у ORB
95 orb.connect! this );
96
97 org.omg.CORBA.Object corbaObject -
98 orb.reaolve_initial_re£erencea( "HameService" );
99 HamingContext naming =
100 HamingContextBelper.narrow( corbaObject };
101 NameComponant namingComponent =
102 new HameComponent( serverHame, "" );
103 HameComponent path[[ = ( namingComponent };
104 naming.rebind( path, this );
105 System.out.println( "Server bound to naming" ) ,-
106 )
107
108 // Уведомляем всех клиентов о завершении работы сервера и
109 // завершаем работу серверного приложения -
110 public void stopServerO
111 i
112 System.out.println( "Terminating server ..." );
113
114 Iterator iterator = null;
115
116 // получаем итератор мложества зарегистрированных клиентов
117 synchronized( clients ) [
118 iterator ■ new BashSet( clients.entrySet() ).iterator{);
119 }
120
121 // посылаем сообщение serverStopping каждому ChatClient
122 while ( iterator.haaNextO ) (
123 ChatClient client = ( ChatClient ) iterator.next();
124 client.serverstopping();
125 Syetem.err.println{ "Disconnected: " + client );
126 }
127
128 // создаем Thread для завершеякя приложения после того, как
129 // stopServer сообщит вызывавшей сторона о завершении работы
130 Thread terminator ■ new Thread(
131 new RunnableO (
132
133 // ждем 5 секунд, выводим сообщение и завершаем работу
CORBA. Часть 1 361
134 public void run()
135 {
136 // засыпаем
137 try (
138 Thread.sleep( 5000 );
139 )
140
141 // игнорируем InterruptedExceptiona
142 catch ( mterruptedException exception ) {)
143
144 System.err.println( "Server terminated" );
145 System.exitt 0 );
146 }
147 )
148 };
149
150 terminator.start() .- // запускаем поток аааершения
151
152 } // завершение метода stopServer
153
154 // метод main, реализующий ChatServerlmpI
155 public static void nain( String[[ args )
156 (
157 // создаем объект ChatServerlmpI и сшзшш его
// с сервисом именования
158 try (
159
160 // создаем объект ChatServerlmpI
161 ChatServerlmpI ChatServerlmpI =
162 new ChatServerlmpI( args );
163
164 Java.lang.Object object = new Java.lang.Object();
165
166 // переводим сервер в режим ожидания
167 synchronized( object ) {
168 object.wait О;
169 1
170 )
171
172 // обрабатываем онибхм создания объекта ChatServerlmpI
173 catch ( Exception exception ) (
174 exception.printstaclcTrаса () ;
175 System.exit( 1 );
176 )
177
178 ) // завершение метода main
179 )
Рис. 7.24. Реализации ChatServerlmpI CORBA-сервера ChatServer
Метод register (строки 81-106) похож на методы register из других примеров.
В данном случае мы достаточно близки к верхушке стека вызовов, который, имея
возможность возбуждать исключение RuntimeException, предохраняет нас от
продолжения действий, если попытка подключения к сервису именования
завершается неудачно.
362
Глава 7
Ш Общая методическая рекомендация 7.4
Хорошая привычка, которую следует усвоить, — это размещение блоков
try в ключевых местах для упрощения, восстановления, после перехвата
вызванных исключений RunttmeExceptiona (даже если это место — main).
В строке 41 метода registerClient мы сохраняем ссылку на слушатель,
используя строковую 10R клиента в качестве ключа в HashtabEe. Метод unregisterClient
(строки 49-59) в строке 75 удаляет объекты-слушатели. За исключением
сохранения (или удаления) IOR обращающихся клиентов мы стараемся использовать
CORBA по минимуму.
Только метод poet Message (строки 62-78) использует сохраненные IOR. В
строке 68 осуществляется извлечение клиентов из Enumeration и помещение в Hash-
Set, а в строках 72-75 осуществляется циклический перебор клиентов и вызовы
метода deliverMessage для каждого из них.
Метод main (строки 155-178) в строках 161-162 создает сервер. Кроме того,
main создает объект object (строка 164) и вызывает objeet.wait (строка 168), чтобы
сохранять активное состояние сервера путем сохранения object в очереди потока.
Основная логика main заключена в блоке try для управления обработкой
возбуждаемых исключений.
7.8.3. DeitelMessenger.java
Следующие сгенерированные файлы доступны для использования на стороне
клиента после компиляции chat.idl:
1. messenger\c Lien t\_Chat Client Stub .Java
2. messenge r\ server ^C hat Server Stub. Java
3. messenger\client\ChatClient.java
4. messenger\cIient\ChatCIientOperatione.java
5. messenger\ChatMessage.Java
6. messenger\server\Chat Server. Java
7. messenger\aerver\Cfaat Server Helper. Java
8. messenge r\eerver\Chat Serve rOpe rat ions Java
Впрочем, как мы упоминали ранее, следующие сгенерированные серверные
файлы доступны клиенту для приема обратных вызовов от сервера:
1. messenger\client\_C hatC lient Imp IBase.Java
2. messenger\cEient\CbatClient.java
3. messenger\client\CbatClientOperations.java
Кроме того, были созданы следующие исходные файлы Java, непосредственно
не используемые:
1. messenger\client\CbatClientHelper.java
2. messenger\cEient\CbatClientHolder.java
3. messenger\Chat Message Helper. Java
4. messenger\ChatMessageHolder.Java
5. messenger\aerver\CbatServerHolder.java
Классы «lata type name>Helper поддерживают прямоэ управление
механизмами потоков объектов брокера объектных запросов. Классы <data type л a me>
Holder поддерживают использование переменных out и inout в соответствии с кодом,
сгенерированным 1DL- компилятором.
CORBA. Часть 1
363
Стандартный клиент CORBA обычно решает вопросы соединения с сервисом
именования, получения объектной ссылки на сервер (например, Chat Server) и
выполнения обращений к серверу. В данном случае клиент берет на себя еще и роль
сервера. Чтобы быть настоящим CORBA-сервером, клиент должен наследовать от
_ChatClientImplBase и реализовывать все методы, объявленные в Chat Client-
Operations. Класс _ChatCIientImpIB*se является реализацией класса ChatClient,
который расширяет ChatClientOperations, поэтому реализация сервере ChatCUent
должна включать реализацию метода deliverMessage. Объявленное на рис. 7.25
в строке 28 наследование от __Cha.tCIientImplBa8e означает, что клиент должен
где-то реализовать метод deliverMessage. Клиент является реализацией Message-
Manager (строка 24) — интерфейса, который представляет ту часть графического
пользовательского интерфейса клиента DeitelMessenger, которая отвечает за
взаимодействие.
1 // CORBAMessageManager.Java
2 // CORBAMessageManager реализует удаленный интерфейс
3 // ChatClient и управляет входящими и исходящими
4 // чат-сообщениями с помощью CORBA
5 package com.deitel.messenger.corba.client;
6
7 // Назови* пакеты Java
8 import java.awt.*;
9 import java.awt.event.*;
10 import Java.util.*;
11
12 // Пакеты растирениж Java
13 import org.omg.CoaHaming.*;
14 import org.onig.CosNaming.NamingContextPackage.*;
15 import org.omg.CORBA.*;
16
17 // Пакет» Deitel
18 import com.deitel.messenger.*;
19 import com.deitel.messenger.corba.client.ChatClient;
20 import com.deitel.messenger.corba.ChatMessage;
21 import com.deitel.messenger.corba.server.*;
22
23 public class CORBAMessageManager extends _ChatCli*ntXmplBase
24 implements HessageManager (
25
26 // входные параметры конфигурации ORB
27 private String[J configurationParameters;
28
29 // слушатели входящих сообщений и уведомлении об отключении
30 private HessageListe.ner messagaListener ,-
31 private DisconnactListener disconnectListener;
32
33 // ChatServer для входящих и исходящих сообщений
34 private ChatServer ChatServer;
35
36 // конструктор CORBAMessageManager
37 public CORBAMessageManager( String[] parameters }
38 (
39 configurationParameters " parameters;
40 }
41
42 // метод подключения к ChatServer
public void connect( HessageListenet listener }
throws Exception
// ицеи удаленный об*ъехт ChatServer
ORB orb = ORB.init( configurationParaa»ters, null ) ,-
org.omg.CORBA.Object corbaObject =
orb.resolve_initial_re£arences( "NameService" );
NamingContext naming =
NamingContextHelper.narrow( corbaObject };
// Получаем ния по объектной ссылке
NameComponent name Component ™
new NameComponent( ChatServer.HAME, "" };
NameComponent path[[ = ( nameComponent );
chatServer =
ChatServerBelper.narrow( naming.resolve( path ) );
// регистрируем клиента у ChatServer для получения сообщений
chatServer.registerClient( this };
// задаем слушатель для входящих сообщений
messageListener = listener;
) // завершение метода connect
// метод отключения от ChatServer
public void disconnect! HeasageListener listener )
throws Exception
ChatServer.unregisterClient( this );
messageListener ** null;
// уведомляем слушатель ос отключении
fireServarDisconnected( "" );
} // завершение метода disconnect
// отправляем ChatMessage серверу ChatServer
public void sendHessage( String fromOser, String message )
throws Exception
// создаем ChatMessage с текстом с
ChatMessage ChatMessage =
new ChatMessage( fromOser, message );
// отправляем сообщение ChatServer
ChatServer.postMessage( ChatMessage >;
) // завершение метода sendHessage
CORBA. Часть* 1
365
99
100 // обрабатываем сообщения ChatMessage от ChatServer
101 public void deliverMessage( ChatMessage message )
102 {
103 if ( messageListener != null )
104 messageListener.messageReceived( message.from,
105 message.message );
106 )
107
108 // обрабатываем уведомление о завершении работы сервера
109 public void serverStopping()
110 (
111 chatserver = null;
112 fireServerDisconnected( "Server shut down." );
113 )
114
115 // регистрируем слушатель для уведомлений об отключении
116 public void setDisconnectListener(
117 DiaconnectListener listener )
118 (
119 disconnectListener = listener;
120 }
121
122 // отправляем уведомление об отключения
123 private void fireServerDisconnected( String message )
124 (
125 if ( disconnectListener != null )
126 disconnectListener.serverDisconnected( message );
127 )
128 ?
Рис. 7.25. CORBAMessageManager - реализация интерфейса MessageManager
с использованием CORBA
Клиент не занимается регистрацией в сервисе именования. У сервера Chat-
Server есть метод регистрации клиента registerClient, поэтому С hat Client (Corba-
MsssengeManager) передает себя в виде входного параметра и становится
доступным для сервера.
Давайте теперь посмотрим на чат-клиент как на клиент. Методами данного
клиента являются connect, disconnect и sendMessege.
Метод connect (строки 43—67) инициализирует брокер объектных запросов,
получает объектную ссылку сервиса именования и запрашивает у сервиса
именования ссылку на С hat Server. В строке 62 осуществляется передача клиента серверу
для выполнения обратных вызовов (также как в примере AlarmClock клиента
следует воспринимать как слушатель. Chat Server как генератор событий, a
ChatMessage как объект-событие, передаваемое слушателю). Если вызов метода register-
Client завершается успешно, то клиент готов получать сообщения от любых
клиентов, соединенных с. ChatServer.
Метод deliverMessage (строки 101-106), реализующий операцию, объявленную
в cbat.idl, принимает входящве сообщение и отправляет его объектам,
зарегистрированным у MessageMaHager в качестве получателей сообщений. Здесь работа
CORBA никак не проявляется за исключением прямых обращений к полям from
и message в строках 104-105. Знав, что ChatMessage является ШЬ-структурой,
нам легче понять, почему мы нарушаем инкапсуляцию. Хорошая новость
заключается в том, что изменение любого из двух указанных полей не оказывает никако-
366 Глава 7
го влияния на других клиентов, получающих данное сообщение (объект является
локальным), во оставляет открытой возможность изменения информации
неконтролируемым образом для любого другого объекта в любом отдельно взятом
чат-клиенте. С точки зрения проектирования ChatMessage должен быть объектом
только для чтения. Нет смысла корректировать уже отправленное сервером
сообщение; другие клиенты не смогут воспринять это изменение. Одно из возможных
решений создать оболочку для ChatServer на стороне клиента в виде прокси-объ-
екта (который позволит нам обрабатывать клиентские исключения CORBA
прозрачно) и сделать так, чтобы прокси-объект сервера ChatServer создавал вокруг
ChatMessage экземпляр оболочки только для чтения, прежде чем отправлять
сообщение чат-клиенту. Однако это проблематично с точки зрения реализации, так
как такой прокси-объект должен был бы сам зарегистрироваться на реальном
ChatServer в качестве исполнителя обратных вызовов вместо регистрации на
ChatServer клиента. В этом случае клиент становился бы исполнителем обратных
вызовов прокси-объекта чат-сервера, если бы прокси-объект решил вызвать метод
клиента deliver Message.
Dei t el Messenger (рис. 7.26), как точка входа в программу, реализующую
клиентский режим, управляет созданием экземпляров объектов CORBAMessageMa-
nager (строки 15-16) и Client GUI (строка 19).
1 // DeitelMessenger.Java
2 // DeitelMessenger использует ClientGOI и
3 // CORBAMaasagaHanager для реализации чат-клиента CORBA
4 package com.deitel.messenger.corba.client;
5
6 // Пакет Deitel
7 import com.deitel.messenger.*;
8
9 public class DeitelMessenger (
10
11 // запускает приложение DeitelMessenger
12 public static void main( String args[] ) throws Exception
13 (
14 // создаем CORBAMassageManager для связи с сервером
15 MeaeageHanager messageManager =
16 пей CORBAMassageManager( arga );
17
18 // конфих>урируем и отображаем окно чата
19 ClientGOI clientGOI = пен ClientGOI( mesaageHanager );
20 clientGOI.aetSize( 300, 400 );
21 clientGOI.setResizable( false };
22 clientGOI.setVisible( true );
23 )
24 )
Рис. 7.26. Приложение DeitelMessenger для запуска чат-клиента CORBA
7.8.4. Выполнение приложения Chat
Для выполнения этого примера нужно сделать следующее:
1. Скомпилировать IDL-файл, например, с помощью idlj (все должно быть
в одной строке):
idlj -pkgPrefix chat com.deitel.advjhtpl.idl -td c:\src
-fall cbat.idl
CORBA. Часть 1
367
2. Реализовать и скомпилировать серверный класс (рис. 7.24).
3. Реализовать и скомпилировать клиентский класс (рис. 7.26).
4. Открыть окно и запустить сервис именования tnamserver:
tnameaerv -ORBInitialPort 1050
5. Открыть окно и запустить сервер:
Java com.deitel.messenger.corba.server.ChatServerImp1
-ORBInitialPort 1050
6. Открыть окно и запустить клиент (затем открыть еще одно окно и
запустить еще один клиент):
Java com.deitel.messenger.corba.client.DeitelMessenger
-ORBInitialPort 1050
7.8.5. Обсуждение
CORBAMessenger был простой копией RMIMessenger. Различия этих двух
версий минимальны, самое большое из них — это то, что использовалась
структура CORBA, а не сериализуемый объект. Количество удаленных объектов,
операций и результат (передача сообщений зарегистрированным клиентам) одинаковы
и в том, и в другом случае.
Зачем нужен локальный объект для сообщений, получаемых клиентами? Как
создание С hat Message в виде распределенного объекта отразится на сложности
приложения?
Текущая схема объекта Chat Mess age — структура, содержащая две строки.
Клиент, чтобы использовать этот объект, который содержит два открытых члена
данных и не имеет методов доступа, создает строку, которая получает доступ
сначала к переменной экземпляра from, затем к переменной message. Если эту
структуру преобразовать в интерфейс, а поля from и message — в атрибуты интерфейса,
это даст следующие преимущества:
1. Корректную инкапсуляцию ChatMessage. Сообщение ChatMessage не
должно демонстрировать свое устройство никому из тех, кто к нему обращается.
2. ChatMessage станет удаленным объектом. Все клиенты, однажды
получившие объектную ссылку ChatMessage, смогут проверять объект и сразу
обнаружат в нем изменения. Кроме того, для данных ChatMessage при
поступлении их к клиенту не нужно будет выполнять демаршалинг.
3. Клиент будет располагать для работы ясно определенным API. Отсутствие
открытых членов данных означает, что объект корректно реализует
сервисы через свои операции, а не путем прямого доступа к данным.
4. Все подключенные клиенты смогут совместно использовать единственный
ChatMessage. ChatMessage станет прямым открытым каналом к клиентам.
Если сервер внесет изменение, оно станет доступно клиентам немедленно и,
когда нужно, клиенты смогут получать доступ к этому сообщению.
Преобразование ChatMessage в интерфейс (то есть в реально распределенный
объект) имеет следующие недостатки:
1. ChatMessage становится удаленным объектом. В качестве распределенного
объекта все зарегистрированные клиенты будут совместио использовать
единственный ChatMessage, превращая сервер в узкое место в сети. Если
какой-то объект располагает существенным количеством информации для
совместного использования клиентами, подумайте о помещении этой
информации внутрь структуры. Это важно по одной причине — в процессе
доступа к распределенному объекту осуществляется маршалинг данных,
преобразовалие протоколов и демаршалинг данных после завершения
вызова (если выполняемая операция связана с возвращением данных),
Накладные расходы иа передачу структуры ChatMessage (полный цикл
сетевого вызова) являются однократными. Накладные расходы на
распределенный ChatMessage начинаются с первого сетевого вызова для получения
объектной ссылки, доступ к атрибуту from требует еще одного сетевого
вызова, к атрибуту message — еще одного, доводя общее число сетевых
вызовов до трех. ChatMessage, как структура, имеет обычные сетевые
издержки при доставке — демаршалинг данных. Дополнительными издержками
является создание объекта для храяения данных, но это фиксированные
начальные издержки при загрузке дополнительно к одному сетевому
вызову. Чем больше данных содержит объект, тем больше выгода от наличия
локальной копии.
2. Все подключенные клиенты могут использовать ChatMessage совместно. Во
многих случаях наличие нескольких клиентов, совместно использующих
распределенный объект является обычной практикой. Однако в данном
случае отсутствие у клиентов их собственных копий означает необходимость
решать вопросы блокирования данных. Как мы узнаем, что никто не
считывает текущее сообщение? Выло бы полеано поведения типа Java synchronized,
но CORBA сама по себе не поддерживает такое поведение (разработчик
всегда может реализовать такое поведение программно). Кроме того,
предоставление другим возможности изменять совместно используемые данные (или
данные, которые потенциально могут использоваться совместно) без
какого-либо прямого механизма уведомления заинтересованных сторон о
соответствующем изменении, нельзя признать хорошим решением.
Отправка локального объекта клиенту поеволяет обойти вес упомянутые
проблемы.
■—^ Совет по повышению эффективности 7.2 _____^_
£?^1 Используйте struct или valuetype всякий раз, когда передаете данные от
клиента серверу и наоборот. Объектная ссылка подходит для решения
задач взаимодействия, но может привести к серьезным накладным
расходам, если используется исключительно для передачи данных. Не только
struct и valuetype являются оптимальным способом распространения
информации, но и распределенный сервис, создавший данные, может также
продолжать регулярно отвечать на запросы.
В RMI, если объект является сериализуемым, он передается через сеть и
собирается в виртуальной машине Java вызывающей стороны. Причина передачи
объекта по значению та же, что и использование struct — доступ к локальной копии
объекта связан с меньшими накладными расходами, чем к удаленному объекту,
требующему повторных обращений. Сериализуеный объект в RMI является
реальным объектом со воеми преимуществами, свойственными объекту. У структуры
CORBA отсутствуют методы. Целевой язык, на который переводится структура,
добавляет какие-то данные (в целевом языке, не поддерживающем объекты,
дополнительные данные практически отсутствуют), но помимо этого брокер
объектных запросов ничего не добавляет. Конструктивно вое IDL-структуры,
преобразованные в классы Java, реализуют org.omg.CORBA.portable.IDLEntity, но не
наследуют ни методы, ни структуру ии от одного класса (за исключением
java.lang.Object). Существует ли в CORBA механизм, функционирующий как
RMI, но поддерживающий свойственную CORBA независимость от языка? Ответом
является да.
COR В А. Часть 1 369
Спецификация Objecteby-Value (OBV — объекты по значению) определяет
новый тип интерфейса — структуру с поведенческой семантикой (способностью
поддерживать объявление операций), называемую valuetype. К преимуществам
valuetype относится способность передавать в рамках распределенной системы
копии «объектов», которые включают как данные, так и методы. Это косвенно
сопоставимо с сериализуемыми объектами Java при сохранении независимости от
языка. Спецификация OBV в качестве официального документа должна
поддерживать концепцию объектов по значению для всех целевых языков,
поддерживаемых OMG.
Реализация концепции объекты по значению — непростое дело. Объявление
valuetype выполняется просто, как следует из рис. 7.27 (строки 7-17).
1// Рис. 7.27. chat.idi
2 // Этот файл содержит IDL-описание
3 // API ChatServer, а также
4 // ChatClient и ChatMessage.
5
6 module obvcorba {
7 valuetype ChatMessage {
8
9 // Свойства ChatMessage
10 private string from,-
11 private string message;
12
13 string getSenderNameO ;
14 string getHessageO ;
15
factory create{ in string from, in string message );
);
// получает ною
void deliverMessage{ in ChatMeaaage message );
module server {
interface ChatServer {
const string NAME = "ChatServer";
// регистрирует новый ChatClient на ChatServer
void registerClient{ Id string chatHame,
in client::ChatClient client );
// открепляет ChatClient от ChatServer
void unregisterClientf in string chatHame );
// передает новое сообщение серверу ChatServer
void postMesaagef in ChatMessage me вtag* );
42 ); // аавержеиие модуля оЬуиеввепдег
Рис. 7.27. chat.idi с ChatMessage, преобразованный к типу valuetype
370
Глава 7
Для создания Java-объекта типа valnetype нужно определить vainetype в IDL-
файле и скомпилировать IDL-описание с помощью idlj (или любого IDL-компиля-
тора, поставляемого производителем программного обеспечения), породить новый
класс из базового определения класса valuetype и обеспечить наличие как
конструктора без параметров, так и конструктора, способного принимать каждое из
полей valuetype, описанных в IDL. Дополнительно к созданным файлам valuetype
компилятор создает исходный файл <uaiue(i/pe>DefaultFactory. Если нужно,
создайте производный класс от < valuet j/pe> Default Factory, сгенерированного IDL,
и добавьте все дополнительные операции, включенные в IDL-фанл. Использование
экземпляров объектов valuetype не отличается от использования любого другого
Java-объекта. На рис. 7.28 приведены некоторые ключевые слова, добавленные
в IDL для поддержки типов valuetype.
Ключевые слова, специфичные для valuetype
valuetype
public
custom
supports
truncatable
Рис. 7.28. Ключевые слова, специфичные для valuetype
Ключевое слово private вводит в заблуждение — private поля valuetype
преобразуются в protected переменные экземпляров Java. Это дает возможность
получать доступ к переменным производному классу, но не внешнему вызывающему
коду {по крайней мере, вызывающему коду за пределами пакета. Правила
видимости в Java разрешают объектам, определенным в пакете, получать доступ к
protected полям другого объекта, определенного в том же пакете.)
Сгенерированный код обрабатывает и все остальное. Сервер создает объект
valuetype, используя метод uew или фабричный метод, но с определением
производного класса. Клиент получает объект valnetype и воспринимает его как
локальный объект. Заглушки и скелеты при получении valnetype вызывают Default-
Factory.
Chat Message вместо того, чтобы быть полученным в законченном виде после
компиляции IDL-описания, теперь является для нас базовым классом. Chat
Message Imp 1.Java {рис. 7.29) является определением класса для создания экземпляров
объекта valuetype.
1 // Рис. 7.29. ChatMessagelmpl.Java
2 package com.deitel.messenger.obvcorba;
3
4 public class ChatMessageImp1 extends ChatMessage {
5
6 // конструктор no умолчанию для пустого объекта ChatMes sage Imp 1
7 public ChatMessageImp1()
8 {
9 thia{ "", "" ) ;
10 )
11
CORBA. Часть 1
371
12 // конструктор для игощиалмаацкм from и свойств сообщения
13 public ChatMessagelmpl( String sender. String text )
14 {
15 from = sender;
16 вез sag* =■ text,-
17 }
18
19 // возвращает имя отправителя
20 public String getSenderNama()
21 (
22 return from;
23 )
24
25 // получает сообщение
26 public String getMeeeagef)
27 {
26 return message;
29 )
30 )
Рис. 7.29. ChatMessagelmpl - реализация ChatMessage
ChatS erverlmpl не изменяется. Сервер получает объект типа ChatMessage (то
есть ChatMessagelmpl) и отправляет этот объект зарегистрированным клиентам.
В DeitelMessenger внесены два изменения В строках 92—93:
ChatMessage message =
new ChatMessagelmpl ( userNaate, messagatToSend };
н в строках 245-246:
messageArea.appendt "\n" + nessage.getSenderNama<) +
"> " + nessage.getMessage() );
Все остальное работает в соответствии с обычными механизмами CORBA,
следовательно объекты DeitelMessenger и ChatS erverlmpl продолжают работать как
описано.
Во время написания CORBAMeseageManager поставляемый с JDK 1.3.0 02 idlj
не поддерживал тип value type в том объеме, как этого требует спецификация OBV.
Представленная программа работает с Java 2 Software Development Kit.
Дополнительные примеры программ будут доступны на www.deitel.com, когда idlj будет
более точно соответствовать спецификации OBV.
7.9. Комментарии и сравнительный анализ
Сравнение RMI-версии чата с CORBA-версией поднимает пару интересных
вопросов, не последним из которых является использование Hashtable вместо Vector
в CORBA-версии чат-сервера. Использовать Vector гораздо проще, чем Hashtable,
но не забудьте, что это элемент реализации. Тип объекта, используемого для
хранения совокупности данных, может изменяться много раз в процессе разработки
подсистемы. В данном конкретном случае объекты Hashtable предоставляют
возможность поиска по первичному ключу, а объекты Vector такой возможности не
предоставляют. Поиск по первичному ключу важен в связи с тем, каким образом
RMI взаимодействует с удаленными объектами по сравнению с CORBA. Прежде
всего, RMI для взаимодействия требует наличия и заглушек, и скелэтов, но JDK
1.2 переходит на протокол реализации с необязательным скелетом. Когда клиент
передает ссылку на свой удаленный объект, клиентский Java-объект, который со-
Глава 7
держит ссылку на RMI-объект, взаимодействует непосредственно с сервером (на
самом деле, «притворяясь» кодом серверного скелета). Лежащий в основе RMI
механизм сопоставляет ссылку на удаленный объект с серверным Java-объект ом,
который содержит объектную ссылку.
CORBA, с другой стороны, обеспечивает взаимодействие заглушки со скелетом
до обращения к серверу. Объектная ссылка CORBA всегда одна и та же, но
Java-объект, хранящий ссылку, отличается. Скелет отвечает за демаршалинг всех входящих
параметров, следовательно Java-объект всегда создается для значений параметров
объектных типов. Креме того, текущая версия JavaTOL поддерживает только
базовые адаптеры объектов (BOA) и не поддерживает переносимые адаптеры объектов
(РОА), что выражается в несколько ином поведении. В RMI Java-объект каждого
клиента идентичен, так как между заглушкой и сервером отсутствует скелет.
Является ли это решение оптимальным для чата? Окончательный ответ дает
ОМА, предлагающая использовать сервисы, где только возможно. CORBA
определяет сервис уведомлений (Notification Service), который распространяет сообщения
в синхронном и асинхронном режимах. Использование Notification Service в
качестве промежуточного ПО для обмена сообщениями делает реализацию чата проще.
CORBA — это одно из мощных средств разработки надежных распределенных
систем. Традиционные приложения превратились в ценные долговременные
активы, а новые приложения — в многократно используемые сервисы, которые могут
обмениваться данными независимо от платформы. ПОР позволяет брокерам
объектных запросов взаимодействовать предсказуемо и надежно, a Object Management
Architecture дает разработчикам ориентиры в процессе проектирования и
реализации систем для решения сложных задач. OMG продолжает расширять
возможности проектирования и интеграции систем посредством разработки спецификаций
в областях, где унификация назначения и проектирования предоставляет
разработчикам больше возможностей для построения больших систем. В главе 8 мы
представим углубленный взгляд на архитектуру систем, включая сервисы CORBA,
компонентную модель CORBA, Enterprise JavaBeans и произведем сопоставление
CORBA и RMI.
7.10. Ресурсы в Internet и во Всемирной паутине
java.sun.com/products/jdk/idl/index.html
На основной странице JavalDL приведен список различных документов, относящихся
к JavalDL и к реализации различных спецификаций OMG.
www.omg.org
Основная страница рабочей группы OMG. Это портал с информацией о CORBA. Здесь
приведена информация о членах OMG и о последних событиях в мире распределенных
www.omg.org/technology/documents/formal/
object_management_axchitecture.htm
С этой HTML-страницы можно загрузить спецификацию Object Management
Architecture в формате PDF или Postscript.
www.omg.оrg/technology/doсuments/formal/index.htm
Официальные документы OMG. Здесь находятся все утвержденные спецификации
в различных предметных областях, поддерживаемых OMG. Приводятся ссылки на
последние спецификации CORBA, сервисов CORBA и IDL-преобразований.
www.omg.org/technology/documents/formal/corbaiiop.htm
Спецификация CORBA 2.4 описывает требования к функциональности продуктов и
к реализуемым кми преобразованиям, чтобы продукты могли считаться CORBA-co-
CORBA. Часть 1
373
www.omg.org/technology/document*/formal/
omg_idl_to_ j ava_language_m*pping. htm
Спецификация OMG преобразований ГОL-Java объясняет как сервисы, описанные иа
IDL, переводятся на Java. Эта спецификации является самым полным источником
информации о преобразованиях.
www.omg.org/tachi)ology/<locunents/formal/naiiilng_eervice.htm
Спецификация сервиса именования описывает базовую функциональность, необходи-
мую распределенному сервису именование, чтобы быть полезным для распределенной
архитектуры приложений. Данная спецификация подробно раэъясэиет определения
и IDL-описания сервиса именования.
www.corba.net
Сайт CORBAnet, спонсируемый OMG и Центром технологий распределенных систем
в Австралии, представляет технологию CORBA и ее применение в конкретных
предметных областях, вилючая Web-приложение, демонстрирующее взаимодействие
между брокерами объектных вепросов разных производителей.
Свободно доступные брокеры объектных запросов
openorb.exolab.org
ExoLab Group's OpenORB яахяетсв открытой реализацией спецификации CORBA.
OpenORB является продуктом совместимым с CORBA 2.4.1 и включает много
стандартных средств, которые разработчики считают необходимыми для полнофункпиональнс-
го продукта CORBA.
kww . cs .trustl. «du/-schmidt/TAO. html
Ace ORB (TAO). Проект АСЕ представляет собой отрытую реализацию структур и
концепций CORBA, а также прекрасного брокера объектных запросов.
Производители программного обеспечения CORBA
Следующие далее производители поставляют брокеры объектных запросов или
продукты, построенные на основе брокеров объектных запросов. Это неполный список.
www.iona.com
Компания Iona.
www.borland.com/visibroker
Компания Borland.
ww. capeclear. com
Компания Cape Clear.
www.vertel.com
Компания Vertel.
Поставщики сервисов CORBA
www.prismtechnologiee.com
Prism Technologies является коммерческим поставщиком продуктов CORBA,
реализовавшим многие из рассмотренных сервисов CORBA.
kww.оос.com
Object Oriented Concepts, Inc. (компания Iona) также является поставщиком
коммерческих брокеров объектных запросов. Программные продукты ООС, которые можно
свободно загрузить {для некоммерческого использования), включают сервисы CORBA
с принадлежащим компании продуктом ORBacua.
openorb.exolab.org/eervicea.html
Те же сотрудники ExoLab Group, которые создали OpenORB, реализовали также
многие из стандартных сервисов CORBA. Эти сервисы CORBA могут быть загружены
свободно.
Резюме
• CORBA — это сокращение от Common Object Request Broker Architecture {общая
архитектура брокера объектных запросов).
• Единственное назначение CORBA — дать возможность программам, написанным из
разных языках программирования, работающим в разных узлах сети, взаимодействовать
друг с другом так же просто, как если бы они находились в адресном пространстве одного
процесса.
• Стратегическая задача CORBA — обеспечение прозрачности.
• Прозрачность вызова характеризует позицию клиента, посылающего сообщение серверу.
• Прозрачность реализации является приложением концепции инкапсуляции к распреде-
• Прозрачность локализации предоставляет клиенту возможность активизировать CORBA-
совместимую программу, которая может выполняться в любом узле сети, независимо от
того, откуда она была вызнана.
• Архитектура управления объектами (ОМА — Object Management Architecture) является
одной из определяющих характеристик, отличающих CORBA от других технологий рас-
предахенных систем.
• ОМА {в качестве эталонной архитектуры) определяет систему, состоящую из веаимодей-
Ствующих сервисов, решающих некоторые прикладные задаче.
• Язык описания интерфейсов (IDL — Interface Definition Language) позволяет
разработчикам описывать в интерфейсе {иди API) тот тип данных, который оня хотят использовать
дистанционно в независимой от языка форме.
• IDL является чисто оннозтельным языком — IDL-фаЙлы не содержат никаких деталей
реализации.
• Компилятор IDL создает несколько ориентированных на конкретные языки
программирования файлы. Прозкту требуется столько компиляторов IDL, сколько языков
программирования используется при разработке системы.
• Любые CORBA-совместимые объекты должны использовать брокер объектных запросов
для выполнения или получения запросов методов, но сами объекты редко имеют дело
непосредственно с брокером объектных запросов.
• Когда клиент посредством брокера объектных вапросов соединяется с распределенным
объектом, брокер объектных запросов еозвращает объектную ссылку на данный объект.
• CORBA опредапяет ннтероперабельные объектные ссылки (IOR — Interoperable Object
Reference). IOR содержит три важных фрагмента информации: локализацию
распределенного объекта {адрес, но не адрес памяти), ссылку на адаптер, создавший IOR, и
идентификатор серванта.
• ПОР — стандартный ORB-протокол, который должны поддерживать все производители,
чтобы их брокеры объектных запросов рассматривались в качестве CORBA-совместнмых.
• ПОР является коакретной реализацией еще одного стандарта OMG — общего межброкер-
ного протокола (GIOP — General Inter-ORB Protocol). GIOP определяет семантику
сообщений, необходимых брокерам объектных запросов для обмена информацией друг с другом,
а также обеспечивает поддержку основного транспортного механизма платформы, на
которой работает данный брокер объектных запросов.
• Java 2 был первым официельным релизом JavaSoft, поддерживающим преобразовании
OMG.
• Для реализации распределенной системы с использованием Java и CORBA необходимы
следующие шаги: 1) выполнить анализ и проектировали©, 2) составить IDL-описаяия. З)
реализовать еврвант с помощью файлов, созданных ЮЬ-компилятором, 4) реализовать
клиеит с помощью файлов, созданных ШЬ-коипилятором для заглушек, 5) запустить
сервис именования CORBA, б) запустить реализацию серванта, 7) запустить клиент.
• IDL поддерживает стандартный синтаксис C++ с двойным сяэшем {//) для однострочных
комментариев.
• Ключевое слово module связывает данное имя непосредственно с пакетом Java.
Объединенные имена вложенных модулей образуют полное имя пакета.
• Фигурные скобки обозначают границы области действия блока и всегда заканчиваются
точкой с запятой.
• Ключевое слово interface определяет CORBA-совместимый объект.
CORBA. Часть 1 375
• Все, что объяахяется в IDL, является открытым, поэтому в нем нет особых ключевых сяов
для обозначения открытых (public), закрытых (private) или защищенных (protected)
объявлений.
• В соответствии с соглашением конкретному илассу, реализующему интерфейс,
определяющий общедоступный API распределенного CORBA-объекта, присваивается имя <имя
интерфейса> Impl.
• Доступ к реализован ному объекту может быть получен только через брокер объектных
запросов.
• Стандартной службой каталогов в CORBA яэляется сервис именования. Единственная
задача сервиса именования — составление списка ресурсов, для последующего нк
использования клиентами.
• Методу resolve_jnitial_references известен список избранных сервисов, непосредственно
доступных данному брокеру объектных запросов. ORB располагает эффективным
мини-сервисом именования, благодаря которому он может осуществлять поиск основных
сервисов.
• Чтобы имя было правильно зарегистрировано (или саявано, в терминологии CORBA),
ресурс должен с помощью объекта NameComponent установить контекст именования.
• Сервисное средство tuameserv является базовой реализацией сервиса именования
объектного сервиса CORBA (COS — CORBA Object Service).
• Брокер объектвых аапросов (ORB — Object Requeat Broker) можно представить в виде
коммутационной панели распределенных систем.
• Архитектура управления объектами (Object Management Architecture) является
эталонной архитектурой OMG для распределенных систем, базирующейся на концепции
брокера объектных запросов.
• ОМА определяет рабочее пространство, в котором объекты, определенные как открытые,
могут с помощью брокера объектных запросов использоваться другим объектом или сер-
• CORBA определяет процесс функционирования брокера объектных запросов, а также то,
как он работает с разными языками программирования.
• Брокеры объектных запросов могут быть реализованы одним из двух способов: в виде
библиотеки или в виде автономных процессов, называемых процессами-демонам и.
• Клиент может взаимодействовать с брокером объектных запросов одним из трех способов:
посредством статической заглушки, динамического интерфейса или напрямую.
• Брокар объектных запросов может взаимодействовать с сервантом одним из трех
способов: посредством статического скелета, динамического интерфейса или напрямую.
• Объектные адаптеры — это объекты, расположенные между клиентом и сервером и
предназначенные для управления доступом к распределенному объекту.
• В CORBA 3.0 был определен объектный адаптер, названный мобильным объектным
адаптером, Portable Object Adapter (POA).
• POA служит нескольким целям, включая возможность отдалить доступ к серванту от
самого серванта.
• Статические заглушки (использующие SII — Static Invocation Interfaoa) имеют жестко за-
програымироваяные объектные типы, чтобы обеспечить возможность проверки типов во
время компиляции, тогда как динамические заглушки (DIE — Dynamic Invocation
Interface) выполняют проверку типов во время выполнения.
• Сервисы CORBA (CORBAservices) являются базовыми сервисами, доступными всем
объектам, подключенным к коммуникационной шине брокера объектных запросов.
• Брокер объектных запросов — это адро CORBA-системы, и сервисы CORBA могут в
процессе функционирования полагаться на наличие брокера объектных запросов.
• Все сервисы CORBA имеют стандартные IDL-интерфейсы, которые описывают функции,
реализуемые раелнчными сервисами,
• Средства CORBA (GORBAiacilities) являются надстройкой над промежуточными
сераясами CORBA и входят в две группы: горизонтальные и вертикальные средства.
• Горизонтальные средства реализуют функциональность клиента.
• Вертикальные средства CORBA, называемые также прикладными областями CORBA
(CORBA Domains), располагаются между сераясами CORBA и объектами-приложения ми.
• Объекты-приложения находятся на самом верхнем уровне ОМА. Они реахизуют фукк-
цнонахьность, отсутствующую на уровнях прикладных областей, средств и сервисов.
• Распределенные объекты необходимо определять таким образом, который дает им
возможность быть обнаруженными и использованными другими респределеииыми объектами.
376 Гпава 7
• Документ OMG formal/99-07-53 описывает преобрезования IDL-Java и охватывает все: от
имен пакетов до вспомогательных приложений для преобразования псевдообъектов
CORBA.
• Пакеты Java, составляющие ядро инфраструктуры CORHA, размещаются в пакетах
с org.omg.*.
• Комментарии из IDL-файла переносятся в генерируемые файлы, только если она
находятся в области действия модуля. Любые другие комментария предвазначены
исключительно для тех, кто сопровождает IDL-файл.
• Структура (struct) — это определение совокупности данных, преобразуемое при
компиляции в определение класса, который удаленный сервант может возвращать клиенту или
принимать от клиента во время выполнения.
• Большинство примитивных типов преобразуется из IDL в Java напрямую. Это относится
к знаковым целым типам (если не считать, что в IDL нет ключевого слона int);
беззнаковые целые типы IDL могут усекаться при преобразовании в свои эквиваленты типов Java
(signed short не может хранить таксе же большее значение, как unsigned short).
• IDL-компилятор осуществляет также инициализацию переменных экземпляров,
устанавливая их в целочисленный ноль, 0.0, false или nail (преобразуя, где нужно, типы).
• Существует два способа объявления массивов в [DL: с помощью илючевого слова seqnenoa
или открывающей и закрывающей квадратной скобок.
• Ключевое слово sequence может использоваться даумя спсеобами: как ограниченная
и как неограниченная последовательности. Если размер заден, sequence рассматривается
как ограниченная последовательность, иначе — как неограниченная.
• Более привычная форма записи с квадратными скобками также может использоваться
при объявлении массила. Однако применение массивов в IDL яэляется нестандартным
для Java. Обычно массив с заданным типом данных определяется с помощью илючевого
слова typedef, нового имени типа данных и размера массива.
• IDL для описания параметров методов использует ключевые слова In, out и inont.
Перемел ная, объявленная нак In, передается вызываемому методу в виде копии. Переменная
out должна быть ссылкой на объект, который содержит другой объект, который, в свою
очередь, может быть заменен еще одним объектом, и это изменение будет видимо илненту
(имитируя вызов по ссылке). Переменная, которая объявлена как inout, может
использоваться и как in, и как oat.
• При каждом объяэлении struct или interface ШЬ-компилятор создает соответствующий
класс Holder, который используется, если struct или Interface являются out-переменными.
• Для всех примитивных типов Java существуют соответствующие им классы Holder в
пакете org.о mg. CORBA, и IDL создает интерфейс Operations, определяя нужный объект
Holder.
• Только в interface могут содержаться объявления attribute.
• Если ключевое слово attribute используется изолированно, компилятор сседает два
метода: аксессор (метод get) И мутатор (метод set).
• При использовании с attribute ключевого слова readonly создается только аксессор.
• В IDL ключевсе слово const объявляет константу.
• Посредники представляют собой заместители других объектов. Они дают илкенту
возможность считать, что он посылает сообщение одному объекту, в то время как на самом
деле он посылает это сообщение другому объекту-
• C0RBA определяет два взаимосвязанных посредника: заглушку и скелет. Заглушка
представляет собой клиентский посредник, а скелет — серверный. Оба посредника скрывают
от клиента и сервера использование брокера объектных запросов.
■ Вариантами статических вызовов в CORBA 3 могут быть синхронные (обычные или
oneway) или асинхронные (обратный вызов или опрос).
• Использование механизмов обратного вызова или опроса — это только одна из
возможностей. Обратный вызов — его вызов, сделанный сервером, а опрос — вызов, сделанный
клиентом. Разница связана с имеющим место условием (сервер извещает объект обратно-
го вызова) или ожидаемой ситуацией (клиент опрешивает объект, чтобы узнать об
изменении состояния).
• Обычный сникронный вызов является стандартным вызовом методов — клиент вызывает
метод и приостанавливает свою работу до завершения работы этого метода.
• Если сигнатура ШЬ-метода содержит ключевсе слово oneway, компилятор создает код,
который не приостанавливает свою работу при вызове, а действует, исходя из установок
CORBA. Часть 1 377
• Спецификация асинхронных вызовов методов CORBA (CORBA Asynchronous Method
Invocation) поддерживает обе модели: и обратный вызов, и опрос.
• Модель обратного вызова поддерживает {с разными опциями QoS) способность серванта
вызывать илиент в произвольный момент времени по усмотрению серванта.
• В модели опроса илиент решает, когда получать результат, который возможно
существует, исходя из вызова oneway-метода.
• При создании экземоляров структур в Java, любая из переменных экземпляров является
открытой.
• Еще один способ для клиента найти сервис — использовать строковую версию ссылки.
• Строковая ссылка я ваяется постоянно действующей, пока сервер может обрабатывать вы-
• Классы <data type лате>Не1рег поддерживают прямее управление такими механизмв-
ми, нак поток объектов ORB.
• Классы <data type name>Holder поддерживают использонавие переменных out и inout
согласно инструкциям, созданным IDL-компилятором.
• Любое обращение к распределенному объекту может вызвать исключительную ситуацию.
■ Разработчики могут найти исилючения, вызываемые CORBA-объектом, заглянув в IDL-
файл соответствующего объекта.
• Ключевое слово raises преобразуется в ключевое слово Java throws.
• Исключения IDL преобразуются в исключения Java, наследуя от java.lang.Exceptfon.
• Стандартные исключения CORBA преобразуются в исключения Java в виде классов final.
• Если исключение CORBA наследует от SystemException, тогда оно является также
R HHtimeExcepti on.
• Причина передачи объекта по значению та же, что и причина использовать структуры —
доступ к локальной копии объекта связан с меньшими накладными расходами, чем к
удаленному объекту, требующему повторных обращений.
• Целевой язык, на который переводится структура, вилючает некоторые дополнения (при
переводе на целевой язык, не поддерживающий объекты, дополнений почти нет).
• Преобразование IDL-структур в классы Java реализует org.orag.CORBA.portable.IDLEntity.
• Спецификация Objecte-by-Value (OBV) определяет новый тип интерфейса — структуру
с поведенческой семантикой (способностью поддерживать объявление операций),
называемую valnetype.
• Преимущество интерфейсов valuetype заключается в их способности передавать копии
«объектов* в пределах распределенной системы, которые включают не только данные, по
и методы.
• Сначала RMI требовал для взаимодействия наличии и заглушек, и скелетов, но в JDK 1.2
RMI перешел на протокол реализации с необязательным скелетом.
• В спецификацию CORBA включен сервис уведомлений, который передает сообщения син-
хроило и асинхронно.
Терминология
activation — активизация
Asynchronous Method Invocation (AMI) —
асинхронный вызов методов
attribute, ключевое слово
Basic Object Adapter (BOA) — базовый
объектный адаптер
Common Object Request Broker Architecture
(CORBA) — архитектура общего брокера
объектных запросов
censt, илючевсе слово
CORBA Component Model (CCM) —
компонентная модель CORBA
CORBAfacilities — средства CORBA
CORBAservices — сервисы CORBA
distributed computing — распределенные
вычисления
Dynamic Invocation Interface (DII) — интер-
exception, ключевое слово
factory — фабрика
General Inter-ORB Protocol (GIOP) — общий
межброкерный протокол
Helper, класс
Holder, класс
idlj, компилятор IDL-Java
ПОР (Internet Inter-ORB Protocol) —
межброкерный протокол Internet
implementation transparency —
прозрачность реализации
in, ключевое слово
Interface Definition Language (IDL) — язык
описания интерфейсов
interface, ключевое слово Portable Object Adapter (РОЛ) — переносе-
Internet Inter-ORB Protocol (ПОР) — меж- мый объектный адаптер
брокерныЙ протокол Intamet proxy — прокси, посредник
Interoperable Object Reference (IOR) — ин- Quality of Service (QoS) — качество обслужи-
тероперабельная объектная ссылка вания
invocation, tranaperency — прозрачность raises, ключевое елово
вызовов readonly, ключевое слово
location transparency — прозрачность ло- Remote Method Invocation {НМД) — удален-
кализацин вый вызов методов
marshaling — маршалннг RMI {Remote Method Invocation) — удален-
module, илючевое слово пый вызов методов
Naming Service — сервис именования sequence, ключевое слово
object activation — активизация объекта servant — сервант
Object Management Architecture {ОМА) — singleton — множество из одного элемента
архитектура управления объектами skeleton — скелет, серверная заглушка
Object Management Group (OMG) — рабо- Static Invocation Interface (SII) — интерфейс
чая группа статических вывовов
Object Request Broker (ORB) — брокер stringified IOR — строковая IOR
объектных запросов struct, ключевое слово
objects-by-reference, спецификация stub — заглушка
objects-by-value, спецификация SystemException, исключение CORBA
oneway, ключевое слово typedef, илючевое слово
opaque network reference — непрозрачная unmarshailng — демаршахинг
value type, илючевсе слово
Упражнения для самоконтроля
7.1. Заполните пропуски в каждом из следующих предложений:
a) является самой высокоуровневой облестью видимости в IDL-файле.
b) В IDL-файле объявления данных без операций имеют вид .
c) IDL interface преобразуется в Java в .
d) В IDL ключевое елово используется для объявления используемого
процедурой исключения.
e) Клиент обычно использует сервис для получения IOR сервера.
f) CORBA-посредник, используемый клиентом, называется , а
СОREA-посредник, используемый сервером, называется .
g) Чтобы отыскать начальную объектную ссылку на стандартный сервис типа сервиса
именования, следует использовать метод ORB .
7.2. Определите, являются ли следующие утверждения истинными пин ложными. Если
ложные, объяспите почему.
a) IDL interface отображается в Java interface с там же самым именем.
b) IDL struct может содержать объявления операций.
c) IDL value type может содержать объявления операций.
d) Единственный способ, которым клиент может получить объектную ссылку на
сервер, заключается в использования сервиса именования.
e) IDL является языком, применяемым для реализации.
Ответы на упражнения для сомоконтроля
7.1. a) module, b) struct, с) Interface, d) raises, e) именования, f) заглушкой,
g) resolve_initial_reference(String service Name).
7.2. а) Истива.
b) Ложь. IDL struct содержит только определения денных.
c) Истина.
d) Ложь. Клиент может прочитать объектную ссылну из строковой ГОН.
e) Ложь. IDL описывает данные, структуры данных и интерфейсы распределе]
объектов, а не их реализацию.
CORBA. Часть 1
379
Упражнения
7.3. Напишите IDL-описание сервера с именем Server, который реализует одну операцию
getString, возвращающую string. Реализуйте сервер. Напишите клиент с именем
Client, который запрашивает строку string и отображает полученное значение.
7.4. Измените упражнение 7.3 таким образом, чтобы сделать следующее:
a) Возвращать структуру, содержащую строку.
b) Измените операцию сервера get. Сделайте, чтобы клиент передавал серверу
структуру.
c) Измените пункт а) так, чтобы клиент изменял объект-структуру, переданную ему
сервером, и отправлял его обратно серверу.
d) Создайте оболочку для реализации сервера (включая код resolve для определения
объектных ссылок) в виде посредника на стороне клиента. Конструктор серверного
посредника, реализованного на стороне клиента, не должен возбуждать никаких
исключений CORBA.
e) Создайте оболочку для структуры в виде объекта в посреднике, реализованном
в пункте d), с помощью фабричного метода.
7.5. Создайте сервис, который возвращает объектные ссылки на Customer (не делайте
Customer структурой). Этот сервис является реализацией паттерна проектирования
фабрики объектов. Создайте в Customer два атрибута и создайте тестовый клиент,
который отображает зги атрибуты (первичный ключ и атрибут).
7.6. Напишите сервер с операцией, возвращающей unsigned short IDL. Сделайте, чтобы
сервер возвращал Short.MAX_VALUE + 1. Отобразите значение, возвращаемое
сервером, в JOptlonDialog. Как отсутствие в Java поддержки ддя беззнаковых целых
значений повлияет на взаимодействие кода, написанного на Java, с серверами, которые
возвращают значения большие, чем Java может воспринять для конкретного типа дан-
7.7. Напишите класс FileServer, который возвращает файл, затребованный клиентом
(используйте массив с элементами IDL-типа octet). Клиент может отображать этот файл
в JTextArea. Какие ограничения связаны с использованием массива IDL?
7.8. Измените FileServer так, чтобы он возвращал объект File Stream. FileStream должен
кэшировать передаваемый файл на сервере пока выполняется запрос клиента. File-
Stream должен иметь операцию, возвращающую за одно обращение одну строку.
Библиография
Balen, Henry, Mark Elenko, Jan Jones, Gordon Palumbo, Distributed Object Architectures
with CORBA, New York, NY: Cambridge University Frees, 2000 (Архитектуры
распределенных объектов и CORBA)
Hoque, Reaz, CORBA 3, USA, IDG Books Worldwide, Inc., 1998
Siege], PhD, Jon, CORBA 3. Second Edition, New York, NY: Wiley Computer Publishing,
2000
Ресурсы Object Management Group
BEA Systems, Inc., et al, «Objects-by-Value, Joint Revised Submission - w/Errata»
(Пересмотренный проект спецификации Objects-by-Value).
www.omg.org/cgi-bm/doc7orbos/98-01-18, February 1998
Object Management Group, «A Discussion of tbe Object Management Architecture»
(Обсуждение архитектуры управления объектами ОМА)
www .omg.org/tecbnology/doe urn ents/fonnal/
object_managenient_architectnre.htm, January 1997
Object Management Group, «The Common Object Request Broker: Architecture and
Specification» (Общий брокер объектных запросов: архитектура и спецификация)
www.omg.arg/cgi-bin/doc?formal/01-02-33, October 2000
380
Глава 7
Object Management Group, «CORBAeervicea: Common Object Services Specification*
(Сервисы CORBA: спецификация общих объектных сервисов)
www.omg.org/cgi-bin/doc7formal/98-12-09, December 1998
Object Management Group, iIDL to Javatm Language Mapping Submission*
(Преобразование IDL-Java)
www.omg.org/cgf-bin/doe7fonnal/01-0e-06, June 2001, p. 1-6-1-7, Table 1-1
CORBA. Часть 2
Цели
• Познакомиться с Dynamic
Invocation Interface (DII).
• Понять различие между
адаптерами BOA, РОА и TIE.
• Познакомиться с сервисами
CORBA, включая сервисы
именования, безопасности,
объектных транзакций
и устойчивых состояний.
• Понять различия между RMI
и CORBA.
• Познакомиться с RMI-ПОР —
средством интеграции RMI
и CORBA.
Для формирования адекватного
представления нужны две
составляющие: явление и суть.
Реми де Гурмон
Если два друга просят
рассудить, кто из них прав,
откажись, иначе одного из
друзей ты потеряешь;
и наоборот, если два
посторонних просят о том же,
соглашайся, один из них станет
тебе другом.
Святой Августин
Глубочайшая мысль или страсть таятся
в заповедной глубине, пока созвучные им ум
или сердце не откроют и не освободят их.
Ральф Уолдо Эмерсон
8.1, Введение1
В предыдущей главе мы взглянули на мир распределенных систем глазами
OMG (Object Management Group). Это коллективное представление (по последним
данным в OMG входит 800 членов), и все рассмотренные проблемы, связанные
с архитектурой, существенны и важны для практики, так как в OMG входят
производители, которые создают (а в некоторых случаях и модернизируют) такие
системы в течение длительного времени. Их совместный опыт и целеустремленность
привели к созданию имеющих широкие перспективы спецификаций, благодаря
которым создание больших корпоративных систем стало возможным и реальным.
Их цель — создание рынка объектов — приближается.
JavalDL — первый шаг в мир CORBA и распределенных систем. Важно
понимать концепции CORBA как на нижнем уровне (на уровне объектных адаптеров),
так и на верхнем уровне (на уровне сервисов и компонентов CORBA).
В написании этой г.
л принимал участие Карлос Валкарсель нз EinTech, Inc.
CORBA. Часть 2
383
B.2. Интерфейс статических вызовов (SII),
интерфейс динамических вызовов (DII)
и интерфейс динамических скелетов (DSI)
Есть два способа выполнить запрос в CORBA: статически и динамически. Во
всех примерах, которые приводились до сих пор, использовался интерфейс
статических вызовов (SII — Static Invocation Interface).
SII основан на определении объектных типов {IDL-интерфейс) и объектных
операций (IDL-методы, принадлежащие IDL-интерфейсу) на этапе компиляции.
Когда IDL-компилятор создает клиентскую заглушку и серверный скелет, не
возникает вопроса, в чем суть соглашения между вызывающей и вызываемой
сторонами. Создаваемый код заглушки/скелета описывает в IDL-файле статический тип
интерфейса. С позиции сервера, он получает запрос, используя входные
параметры, выполняет код и возвращает все требуемые значения. Серверный брокер
объектных запросов получает все необходимое для выполнения запроса, за
исключением информации о процессе, создавшем данный запрос.
Способность серверного скелета принимать запрос независимо от механизма
создания запроса, делает возможным динамическое определение вызова сервера.
У клиента есть два возможных варианта вызова сервера: использовать статические
заглушки, созданные IDL-компилятором или вручную запрограммировать запрос
на вызов. Клиент использует API интерфейса динамических вызовов (DII —
Dynamic Invocation Interface) для создания и отправки запроса на выполнение
непосредственно серверному брокеру объектных запросов без участия заглушки.
Код, написанный разработчиком с использованием DU. API, выглядит так же, как
код, сгенерированный для заглушки, Различие заключается в дополнительных
возможностях программного управления посредством API.
На другой стороне цепочки вызова сервер может обработать этот запрос двумя
способами: входящий запрос может обработать статический скелет, соаданяый
IDL-компилятором, или это может сделать вручную запрограммированный
сервант (реализация объекта, управляемая объектным адаптером). При получении
вызова и «ручном» извлечении данных, необходимых входящему запросу, сервант
использует интерфейс динамических скелетов (DSI — Dynamic Skeleton
Interface). Клиенты и серверы могут использовать DII и DSI совместно или независимо.
Клиенты, использующие эти интерфейсы вызова, могут вызывать операции
сервера независимо от того, статические или динамические скелеты он использует.
Серверы могут принимать входящие запросы от клиентов независимо от того,
использует ли клиент статические заглушки или динамические вызовы. За передачу
параметров при вызове, правильность их типов отвечают разработчики.
Репозиторий интерфейсов (IR — Interface Repository) содержит информацию
описательного характера о распределенных объектах. Она включает информацию
об имеющихся модулях, об интерфейсах, определенных в модулях, об именах
операций, определенных в интерфейсах, о типах параметров, принимаемых
операциями, о тинах возвращаемых операциями значений и обо всех возбуждаемых
исключениях. Клиент может получить эту информацию об объекте, из ал екая
метаданные из репозитория интерфейсов.
JavalDL поставляется без репозитория интерфейсов, поэтому по-настоящему
динамически функционирующий пример DII невозможен с брокером объектных
запросов, поставляемым с JDK 1.3. Можно привести пример базового кода — мы
можем подключиться к удаленному объекту напрямую и, используя известную
сигнатуру операции, осуществить динамический вызов операции данного объекта.
Последовательность действий, необходимая для осуществления DH-вызова (без
использования репозитория интерфейсов) имеет следующий вид:
384
Глава 8
1. Получить объектную ссылку па серверный объект.
2. Создать и инициализировать объект Bequest.
3. Вызвать Request и дождаться завершения вызова.
4. Получить результаты.
Использование репозитория интерфейсов означает, что после получения
объектной ссылки на серверный объект, необходимо найти элемент репозитория для
данного объектного типа. Далее приведена соответствующая последовательность
действий:
1. Получить объектную ссылку ва серверный объект.
2. Найти нужный метод в репозиторин интерфейсов.
3. Создать список параметров для целевой операции, используя определение
операции (OperatlonDef) репозитория интерфейсов.
4. Создать и инициализировать объект Request.
5. Вызвать Request и дождаться завершения вызова.
6. Получить результаты.
Возможный благодаря Dn уровень управления оплачивается ценой
усложнения программного кода. Разработчики должны пунктуально осуществлять вызов
каждого метода, а количество добавляемого ими кода изменяется в зависимости от
сложности параметров, типов результирующих значений и исключений. И все же
часто разработчики считают, что те возможности, которые предоставляет DII,
стоят того, чтобы его освоить. Системные архитекторы считают, что Dtl полезен при
реализации компонентов с интенсивным обменом данными, модулей
администрирования и в некоторых других случаях. Репозиторий интерфейсов становится
центральным хранилищеи данных, ■ котором хранятся зарегистрированные
объектные типы — поставщики CORBA несут ответственность за реализацию надежных,
многопользовательских рапозиториев, обеспечивающих поддержку
разрабатываемых в настоящее время глобальных распределенных систем с высокой пропускной
способностью.
Мы внесли изменения в пример SystemClock, чтобы включить поддержку ОП.
Файлы, необходимые для SystemClock: cIock.idl, SystemCIock.java, SystemClock-
Operationsjava, SyetemClocklmplBaee.java, SyetemClocklmpl.java и SystemClock-
Client.java. Нужно сгенерировать только серверные файлы, поэтому для idlj следует
задать опцию —fserver вместо —fall. Файлы Helper и Holder не нужны. System-
ClockClientjava (рис. S,l) — единственный файл, требующий внесения изменений.
1 // SyetemClockCliant.java
2 // Клиент, мспояъаужвнй DII для аапрося
// системного времаюг у еерааята.
3 package com.daital.advjbtpl.idl dii;
4
5 // Базовые пакеты Java
6 import Java.text.DateTormat,-
7 import java.util.*;
8
9 // Пахеты распираний Java
10 import javax.swing.JOptionPane;
11
12 // Пакеты OHG CORBA
13 import org.ong.CORBA.ORB;
14 import org.omg.CoeNamiog.*;
15 import org.(3mg.CoaM*ming.M*mingCont«»tPackage.*;
CORBA. Часть 2
385
16
17 public class SyBtemClockClient implements Runnable {
18
19 // объектная ссылка на искомый сервер
20 private org.omg.CORBA.Object time5erver;
21 private ORB orb;
22
23 // инициализирует клиент
24 public SystemClockCLient( String!] params ) throws Exception
25 1
26 connectToTimeServer( params );
27 startTiroerO :
28 1
29
30 // используем NameService для подключения к серверу времени
31 private void connectToTimeServer( String [] params )
32 throws org.omg.CORBA.ORBPackage.XnvalidName,
33 org.omg.CosNaming.NamingContextPackage.InvalidName,
34 NotFound, CannotProceed
35 {
36 // Подключаемся к серверу SystemClock
37 orb = 0RB.init( params, null );
38
39 org.omg.CORBA.Object corbaObject =
40 orb.resolve_initiapreferences{ "NameService" );
41 Ham i ngCon text naming =
42 HamingContextHelper.narrow( corbaObject );
43
44 // Преобразуем объектную ссылку в имя
45 NameComponent nameComponent =
46 new MameComponent{ "TimeServer", "" );
47 NameComponent path[) = { nameComponent );
48 timeServer = naming.resolve( path );
49 1
50
51 // запускаем поток таймера
52 private void startTimer()
53 {
54 Thread thread = new Thread.( this ) ;
55 thread.start();
56 )
57
58 // регулярно опрашиваем сервер и отображаем результаты
59 public void run()
60 {
61 long time =0;
62 Date date = null;
63 DateFormat format =
64 DateFormat.getTimeInstance( DateFormat.LONG );
65 String timeString = null;
66 int response = 0;
67
68 org.omg.CORSA.Request request =
69 timeServer.^request( "currentTimeMillis" ) ;
70 request.set_retum_type( orb.get_primitive__tc(
71 org.omg.CORBA.TCKind.tk_longlong )
while{ true ) (
// ишшоеи катод currentTimeMillis с помощь» объекта запроса
// time = timeServer.currentTimeMJ.llis();
request.invoke();
// получаем ив объекта-запроса значение времени
time = request.result{).value().extr*Ct_longlong{);
date = new Date( time );
tiweString = format.format( date );
response = JOptionPane.showConfirmDialog( null, timeString,
SysteraClock Example", JOptionPane.OK_CAHCEL_OPTION );
if ( response ™ JOptionPane.CANCEL_OPTION }
92 System.exit( 0 );
93 )
94
95 // метод sain клиентского приложения
96 public static void main{ String args[] )
97 <
98 // создаем клиента
99 try (
100 new SystemClockClient{ args );
101 )
102
103 // обрабатываем исключения, возбуждаемые а процессе работы клиента
104 catch ( Exception exception ) [
105 System.out.printlnj
106 "Exception thrown by SystemClocltClient: " );
107 exception.printstackTrace();
108 )
109 )
110 1
Рис. S.I. SystemClockClient. модифицированный с целью поддержки DM
Во-первых, нам нужны две переменные экземпляров для использования в
клиенте. В строке 20 объявляется ссылка на CORBA-объект с именем timeServer,
который будет хранить ссылку на удаленный серзер. Переменная orb (строка 22)
хравит ссылку на брокер объектных запросов, используемую в методах connect-
ToTimeServer (строки 31-49) и пш (строки 59-93), для подключения клиента
к серверу и для создания вспомогательных объектов для вызова сервера. Метод
connectToTimeServer помещает вновь созданный объект брокера объектных
запросов в переменную orb и подключает клиент к серверу timeServer.
Вызов серверного метода currentTimeMillis осуществляется в строке 78
(первоначальный код закомментирован в строке 77). До цикла while клиент запрашивает
CORBA.Object, возвращенный в результате вызова метода resolve (сохраненный
в переменной timeServer), для того, чтобы создать и вернуть объект Request. Этот
CORBA.Object (клиентская конструкция, в которой хранится объектная ссылка
CORBA. Часть 2
387
на сервер) возвращает объект Request, который используется клиентом для
вызова сервера. Единственной задачей клиента является приведение типа request
возвращаемого значения к ШЬ-типу long long (TCKAad.tk_Ionglong). Вызов current-
TimeMillis не требует никаких входных параметров, поэтому подготовка к дина-
инческому вызову завершается, как только тип возвращаемого значения данного
метода приводится к типу request (строки 70-71).
Вызов cuirentTimeMiUis превращается в состоящий из нескольких строк
динамический вызов целевого метода timeServer (в строке 78 вызывается метод, а
строке 81 осуществляется демарш ал ин г возвращаемого значения). Вызов invoke
приводит к тому, что объект request отсылается подключенному серверу, работа
метода приостанавливается до получения возвращаемого операцией значения. Клиент
запрашивает объект request, чтобы получить result; result используется для
получения находящегося в нем значения value, а само значение запрашивается в виде
lDL-типа long long (строка 81).
Работая с сервисом именования (tnameserv), SystemCIocklmpl и SystemCloek-
Client дадут тот же результат, что и пример SystemCIock в главе 7 с
минимальными дополнительными издержками. Косвенный вызов cnrrentTimeMillis не требует
никаких параметров, а сам пример не требует воссоздания объекта Request для
вызова метода этого удаленного объекта.
DII включает несколько полезных технологий. Использование репозитория
интерфейсов предоставляет дополнительную возможность больше узнать о типах,
имеющихся в распределенной системе во время выполнения, и выдать клиенту
информацию для в зан мо действия с распределенными объектами, которые появятся
в будущем. Репозиторий интерфейсов (и API для доступа к нему) можно
воспринимать как распределенную версию механизма отражения Java. Гибкие среды могут
навлечь пользу из информации, включенной в репозиторий интерфейсов, также
как и заинтересованные клиенты, которым нужна информация о типе
конкретного CORBA-объекта, с которым они работают в данный момент времени.
JavaiDL не является полной реанивацией CORBA- Java-документация пакета
org.omg.CORBA перечисляет рал личные фрагменты пакетов JavaiDL,
определенных в API, но не реелизованных в JavaiDL. Вместо того чтобы решать проблемы
с реализацией Sun, мы рекомендуем после завершения моделирования и аналива
базирующейся па Java распределенной системы использовать реализацию CORBA
производителей коммерческих брокеров объектных запросов. Кроме того,
доступны реализации CORBA для Java с открытыми исходными текстами, которые
включают действующий репозиторий интерфейсов.
8.3. Адаптеры BOA, POA и TIE
Во взаимодействии клиента с распределенным объектом участвуют не только
два брокера объектных запросов. Анализируя реализацию CORBA-систем, мы
видим, что клиентов целенаправленно делаются «тонкими». В распределенных
системах основная нагрузка всей тяжестью ложится на сервер, тогда как все вопросы
взаимодействия на стороне клиента берут на себя сгенерированные с помощью IDL
заглушки. Клиентов не касается выравнивание нагрузки или перснстеятиость —
всем этим занимаются серверы. Промежуточным средством, допускающим такой
несимметричный подход, является объектный адаптер, который располагается
между распределенным объектом и его брокером объектных запросов (рис. 7.8)-
Доступ ко многим сервисам брокера объектных запросов, используемым
распределенным объектом, осуществляется через объектный адаптер [1]. К сервисам
брокера объектных запросов, которые прозрачно выполняются объектным адаптером,
относятся генерация IOR, обеспечение безопасности и активация/девактивация. >
Первым специфицированным OMG объектным адаптером был базовый объектный
388
Глава В
адаптер (Basic Object Adapter или BOA). В CORBA 2.0 определение BOA было
нечетким, поэтому имела место несовместимость разных реализаций BOA, которая
затрудняла переносимость и взаимодействие сгенерированного кода,
используемого брокерами объектных запросов разных производителей. Чтобы решить эти
проблемы, OMG ограничил применение адаптеров BOA в пользу переносимых
объектных адаптеров (РОА— Portable Object Adapter). Предлагаемые РОА средства дают
гораздо больше возможностей и облегчают взаимодействие. Адаптеры BOA проще
использовать (даже если их поведение недостаточно последовательно), но
адаптеры РОА используются гораздо более широко.
РОА использует код, находящийся в скелете, который сгенерирован IDL-kom-
пилятором, и соединяет объектную ссылку с кодом, написанным разработчиком.
Адаптеры РОА, как настраиваемые объекты, предоставляют больше возможностей
управления реализацией объекта (при подключении к РОА, называемым
сервантом). Также как разрабатываемый нами сервер наследует от определения Impl-
Base (рис. 7.4, строка II), сервант наследует от базового определения РОА,
сгенерированного IDL-компилятором. В данном случае разрабатываемый нами
распределенный объект наследует структуру, необходимую объекту, используемому
РОА, а РОА управляет доступом к серванту, основываясь на политиках, заданных
в РОА. Политики описывают свойства объектов, управляемых конкретным РОА.
Однажды установленные, эти политики остаются неизменными.
Рассмотрим три политики РОА: Implicit Object Activation, IDAssignmentPolicy
и RequestProcessingPoiiey (всего существует семь политик). Политика Implicit-
Ob jectActivation, устанавливаемая с помощью константы NO_IMPLICIT_ACTI-
VATION, сообщает РОА, что внешний объект создал управляемый РОА сервант
и передал этот объект РОА, используя методы activate_object или activate_ob-
ject_with_id. Идентификатор объекта •— это уникальный идентификатор,
который может быть задан как разработчиком, так и системой. Каждый
идентификатор объекта связан С конкретным сервантом. Активация объекта в РОА делает
возможным его использование клиентами. Дезактивация объекта делает его
недоступным, хотя такой объект может быть снова активизирован позднее.
Использование методов activate_object или activate_object_with_ld основано на
использовании политики IDAssignmentPolicy, которая может быть задана как
с помощью константы user_id, так и константы system_id. nser_id в политике
IDAssignmentPolicy означает, что вызывающая сторона, которой нужно связать
сервант с РОА, сама определит уникальный идентификатор серванта вместо того,
чтобы предоставить РОА сгенерировать идентификатор этого объекта (случай,
который имел бы место, если бы использовались IDAssignmentPolicy и system_id).
Константа USE_DEFAULT_SERVANT вместе с политикой RequestProcessingPoiiey
(и методом set_servant), а также упомянутые выше политики сконфигурируют
РОА так, что он не будет создавать серванты (но будет направлять их вызовы),
свяжет назначенные пользователем идентификаторы объектов с различными
сервантами и направит все вызовы серванту, назначенному по умолчанию, если
идентификатор объекта не соответствует уже активированному объекту. Примером
такого взаимодействия может быть следующее: клиент запрашивает конкретный
сервер, используя объектную ссылку этого сервера. В объектной ссылке
содержится кроме всего прочего объектный идентификатор серванта. РОА, обрабатывая
данный вызов в соответствии с политикой RequestProcessingPoiiey, использует
идентификатор объекта или для поиска серванта, которому соответствует данный
идентификатор объекта, или для вызова серванта, назначенного по умолчанию,
который использует данный идентификатор объекта для поиска в базе данных.
Если вызван сервант, назначенный по умолчанию, то он может создать
запрашиваемый объект, вернуть новый объект РОА, а РОА передаст его брокеру объектных
запросов и дальше клиенту.
CORBA. Часть 2 389
Если явной потребности в идентификаторе не существует, РОА может создать
идентификатор для внутреннего применения. В любом случае, когда поступает
запрос конкретному серванту, РОА использует сочетание политик и объектных
идентификаторов для передачи данного запроса конкретному объекту. Это дает
возможность одному РОА управлять одним или несколькими сервантами.
Хотя комбинирование политик РОА может внести путаницу, оно дает
возможность масштабирования и различных уровней QoS.
Еще один способ, которым разработчики могут использовать РОА, — это
поместить свои серванты в оболочку адаптеров TIE. Адаптеры TIE позволяют
взаимодействовать с РОА в условиях, когда объектная реализация серванта не наследует
структуру от POAImpl. Сервант, делегирующий функциональность CORBA
объекту TIE, может наследовать от какого-нибудь другого базового класса (в Java мы
используем наследование только тогда, когда это нужно). Объект TIE, который
реализует API серванта, заключает в себе созданный сервант и осуществляет вызовы
нужной операции во время выполнения.
!_^. Хороший стиль программирования 8.1
Р?> Если вы используете адаптеры РОА, подумайте об использовании TIE
РОА, Потенциально распределенный объект может наследовать от
интерфейса Operations и прозрачно получать функциональность CORBA от
TIE РОА.
BOA, РОА и TIE зависят от кода, создаваемого IDL-компилятором. IDL-компиля-
тор idlj, поставляемый с ЛЖ, пока еще не поддерживает генерацию адаптеров РОА
и TIE, поэтому разработчикам рекомендуется использовать реализации CORBA
других производителей, если проект их системы подразумевает использование
адаптеров РОА и TIE.
8.4. Сервисы CORBA
Сервисы CORBA (CORBAseruices) — это объектные сервисы (Object Services)
архитектуры Object Management Architecture (рис. 7.7). Сервисы CORBA определяют
базовые сервисы и вспомогательную структуру, полезную для многих
приложений. Пять сервисов, описываемых в данном разделе, являются наиболее широко
используемыми (и уже вошли в состав сервисов, специфицированных в
компонентной модели CORBA (CCM — CORBA Components Model) — CORBA-эквивален-
та Enterprise JavaBeans.
8.4.1. Сервис именования
Сервис именования ставит в соответствие именованным объектам некоторое
произвольное значение, называемое привязкой имени. Может использоваться любое
значение, если только оно наследует от org.omg.CORBA.Object. В контексте
CORBA-системы связь устанавливается между именем и объектной ссылкой. Не
накладывается никаких ограничений ни на использование сервиса именования,
ни на однородность включаемой в нее совокупности объектов. Путь к привязке
имени состоит из нуля и более контекстов именования (совокупности
уникальных привязок имен). Разрешение имени внутри сервиса именования приводит
к возврату объекта, связанного с данным именем. Привязка имени к сервису
именования устанавливает связь между именем и объектом. Связывание имен с
контекстами именования приводит к созданию графа именования, который
представляет собой отображение всех возможных путей, ведущих к узлам, в которых
хранятся привязки имен. Несколько привязок имен могут указывать на один и тот же
объект.
390
Глава 8
Именование является одним из основных сервисов C0RBA. Некоторые из
сервисов C0RBA наследуют ЮЬ-интерфейсы от других сервисов CORBA как с целью
обеспечения совместимости, так и для согласования их API. Именование не
зависит от других ШЬ-интерфейсов, спецификация не навязывает ни способов
организации этого сервиса, ни того, что должны означать привязки имен. Все, что клиент
может ассоциировать с именем, может быть включено в сервис именования. Кроме
того, группа сервисов именования может являться частью графа именования,
чтобы обеспечить нсеможность распределенного разрешения имен.
8.4.2. Сервис безопасности
Сервис безопасности — самый сложный из сервисов CORBA. Он состоит из
двух уровней. Уровень 1 предусматривает базовый уровень безопасности:
1. Аутентификацию пользователей.
2. Безопасность вызовов.
3. Возможность аутентификации администраторов для приложений,
поддерживающих механизмы безопасности.
Первый уровень позволяет приложениям игнорировать требования системной
безопасности. Потребности обеспечения безопасности имеют ограниченную
область действия, а брокер объектных запросов обычно сводит эти потребности
к полной анонимности объектов, вовлечённых в цепочку вызова. Обычный брокер
объектных запросов не заботится о дополнительной обработке вызовов объектов.
Однако, чтобы могла функционировать распределенная система безопасности, те
брокеры объектных запросов, которые допускают выполнение функций
безопасности, должны поддерживать передачу полномочий от инициатора вызова (и от
любых промежуточных объектов) целевому объекту.
Первый уровень безопасности требует поддержки моделей без делегирования и
с простым делегированием, между клиентом, вызываемым объектом и конечным
целевым объектом. В модели без делегирования клиент передает полномочия
промежуточному объекту — этот объект создает промежуточные полномочия для
использования при вывове конечного целевого объекта. В модели с простым
делегированием промежуточный объект для вызовов целевого объекта может
использовать полномочия клиента. Если нет ограничений на то, как промежуточный
объект может вызывать целевой объект, используя полномочия клиента, то такой
промежуточный объект играет роль самого клиента [2].
Второй уровень обеспечивает все то, что обеспечивает первый уровень, и
добавляет следующие возможности обеспечения безопасности:
1. Более гибкая аутентификация пользователей (т.е. аутентификация
пользователей может осуществляться при вызове метода).
2. Более высокий уровень безопасности при вызовах.
3. Аудит.
4. Более совершенное управление защищенными вызовами.
5. Делегирование.
6. Администраторы могут устанавливать политики безопасности.
7. Приложения, поддерживающие механизмы безопасности, могут получать
информацию о политиках безопасности.
8. Брокеры объектных запросов и другие сервисы могут определять, какие
политики безопасности используются.
CORBA. Часть 2 391
Второй уровень безопасности дает системе возможность полностью использовать
всю функциональность, перечисленную ранее (посредством использования
интерфейсов АРГ, определенных в различных интерфейсах сервисов безопасности).
Отдельные программы администрировали я изменяют политики безопасности.
8.4.3. Сервис объектных транзакций
Сервис объектных транзакций OTS {Object Transaction Service) дает
возможность CORBA-объектам выполняться в качестве составных частей распределенных
транзакций. Спецификация OTS была одной ил первых спецификаций сервисов
CORBA, недавно в нее были внесены изменения с целью повышения ее гибкости
и преодоления различных проблем, связанных с реализацией. Транзакция
описывает совокупность операций, в которых многочисленные пользователи могут
получать доступ к данным и/или изменять их, но целостность данных гарантируется
(транзакция известна и в системах управления базами данных как единица
работы) . Акроним АСШ (Atomic, Consistent, Isolated, Durable) описывает четыре
стандартных требования, предъявляемых к надежным транзакциям:
• Атомарность (atomic) — завершение транзакции означает или полный успех,
или полную неудачу; если для завершения транзакции, должны были быть
выполнены пять шагов, тогда нли все пять шагов должны завершиться
успешно, или изменения, внесенные на каждом шаге должны быть
аннулированы. В атомарной транзакции никогда не может быть ситуации, при
которой некоторые из шагов выполнены, а другие — нет.
• Согласованность (consistent) — последствия транзакции являются
воспроизводимыми и предсказуемыми. Выполнение тех же шагов с тем же набором
данных должно всегда давать один и тот же результат.
• Изоляция (isolated) — транзакция не может быть прервана извне и не дает
указаний на то, как осуществляется ее выполнение (последовательно или
параллельно).
• Долговременность (durable) — не считая катастрофических сбоев
(отключение электроэнергии, землетрясение и т.д.), результаты транзакции
фиксируются и хранятся на долговременной основе.
Существует два варианта завершения транзакции: фиксация (изменения
становятся постоянными) или откат (все изменения аннулируются). OTS добавляет
к свойствам сервиса надежных транзакций способность управления транзакциями
в рамках распределенной системы.
С функциональной точки зрения сервис объектных транзакций поддерживает
плоские и (факультативно) вложенные транзакции. Наиболее общим типом
являются плоские транзакции. Вложенные транзакции поддерживают АСШ на время
выполнения дочерней транзакции и кроме того предусматривают возможность
частичного отката, если происходит ошибка при выполнении дочерней транзакции. Когда
не достигает успеха транзакция самого верхнего уровня, т.е. транзакция, у которой
нет родителей, то осуществляется откат всей транзакции полностью.
OTS позволяет разработчикам добавлять распределенные транзакции к
существующим системам. CORBA облегчает объединение гетерогенных систем. OTS в
сочетании с такими стандартами как Open Group's XA Specificetion [4] дает клиенту
возможность использовать транзакции как явно, так и неявно. При неявном
использовании транзакции после ее создания контекст транзакции прозрачно
передается от брокера к брокеру объектных запросов. Явная транзакция передает
контекст транзакции в качестве параметра всех вызываемых методов. OTS также
предоставляет серверам возможность регистрироваться в сервисе транзакций.
С целью обеспечения возможности объединения гетерогенных систем OTS подцер-
392
Глава 8
живает также модель обработки распределенных транзакций Х/Ореп (Х/Ореп
Distributed Transaction Processing), которая позволяет OTS взаимодействовать
с системами процедурных транзакций.
В CORBA 3 характер выполнения асинхронных вызовов в распределенной
системе определяет спецификация асинхронного вызова методов {AMI — Asynchro
nous Method Invocation). В CORBA-системе характер асинхронных вызовов почти
полностью определяется клиентской стороной; у сервантов есть свои требования
к транзакциям, устанавливаемые через их РОА, и клиенты, выполняющие
асинхронные вызовы, должны допускать обработку своих вызовов промежуточными
маршрутизаторами, которые будут создавать новый контекст транзакции,
учитывающий конкретный сервавт.
Адаптеры РОА определяют типы транзакций, поддерживаемых сервантами,
разрешая или требуя только разделяемые транзакции, разрешая или требуя
только неразделяемые транзакции, или разрешая ияи требуя и тот, и другой тип
транзакции. Если политика транзакций устанавливается на уровне РОА, то это
означает, что все объекты (серванты), управляемые данным РОА, имеют одну и ту же
политику транзакций. РОА может одновременно управлять несколькими объектами,
поэтому у всех объектов, находящихся под управлением данного РОА, одни и те
же требования к транзакциям.
OTS определяет концепции транзакционных клиентов, транзакционпых объ
ектов и восстанавливаемых объектов. Транзакционный клиент взаимодействует
с OTS с целью создания транзакции и последующей ее фиксации или отката.
Транзакционный объект реагирует на вызов из транзакции, но данные такого объекта
могут быть невосстанааливаемымк. Восстанавливаемый объект — это
транзакционный объект, данные которого являются восстанавливаемыми (например,
объект, представляющий собой запись базы данных). Восстанавливаемые объекты
защищают свои данные и, в случае сбоев, помогают восстановить их целостность.
Двумя типами серверов приложений, использующих транзакционные объекты,
являются транзакционные и восстанавливаемые серверы. У транзакциониого
сервера нет восстанавливаемых объектов, тогда как у восстанавливаемого сервера
имеется по крайней мере один восстанавливаемый объект. В OTS определено, что
оба типа серверов могут выполнять откат транзакции, но участвовать в фиксации
транзакции может только восстанавливаемый сервер [5].
Сервис транзакций Java (JTS — Java Transaction Service) — это
Java-реализация сервиса распределенных транзакций. API JTS определяется С помощью Java
Transaction API {JTA). JTS использует спецификации CORBA OTS для
определения протокола обмена контекстами транзакций между менеджерами транзакций.
8.4.4. Сервис устойчивых состояний
Сервис устойчивых состояний {PSS — Persistent State Service) отвечает за
сохранение и выборку объектов. В сочетании с OTS PSS абстрагирует
взаимодействие между объектами и хранилищами данных. Теоретически системы должны
сохранять свои объекты в объектных хранилищах данных. Однако на рынке
доминируют реляционные базы данных — обеспечить хранение объектов в этих базах
данных последовательным и предсказуемым образом дело непростое. Сервис
устойчивых состояний с целью обеспечения целостности транзакций и управления
доступом опирается на сервис объектных транзакций и сервис безопасности.
Так же, как IDL описывает интерфейс распределенного объекта, новый язык
описания устойчивых состояний (PSDL — Persistent State Definition Language)
описывает структуру распределенного объекта в переносимом виде (PSDL
является расширением IDL). Двумя новыми конструкциями являются storage type и sto-
ragehome (используемые с ключевым словом abstract или без него). PSDL-файл со-
CORBA. Часть 2
393
держит два типа определений: абстрактные и конкретные. Кроме того, можно
описать фабрику, которая создает объекты вновь определенного типа. Конструкции
abstract storage type или abstract storagehome не являются описаниями
определений конкретных объектов — скорее, они являются описанием переносимого
определения устойчивого состояния С ORBA-объекта. Концепции и конструкции
фабрики объектов и объектов, создаваемых фабрикой, должны выглядеть знакомыми
для всякого работающего с Enterprise JavaBeans (Java-интерфейсы EJBHome
и EJBObject являются соответствующими базовыми классами, используемыми
при определении компонентов EJB). На рис. 8.2 приведен пример storagehome
и storage type для объекта Customer.
1 // Структура объекта предметной области. Это абстрактное определение,
2 // нужное для P5S. Конкретное определение Customer находится дальше.
3 abstract storagetype Customer {
4 // accountNumbar — это наш первичный ключ
5 readonly state string accountNumbar ,-
6 state string name;
? );
8
9 // Фабрика, используемая для получения объектов Customer
10 abstract storagehome CustomerHome of Customer {
11 // Данный метод будет создавать устойчивые объекты Customer
12 Customer create{ in string accountNumbar );
13 );
14
15 // Наш определитель фабрик. Использует CustomerDirectory для
16 // поиска всех фабрик, используемых системой для создания
17 // объектов предметной области типа Customer
18 catalog CustomerDirectory {
19 provides CustomerHome customerHome;
20 };
21
22 // Это конкретное объявление Customer, определенное
23 // выше. Эти объявления пусты, так как мы не добавляем
24 // никаких дополнительных структур в Customer или его фабрику.
25 storagetype СиstomerImp1 implements Customer {};
26
27 storagehome CustomerHomeImp1 of Customerlmpl
28 implements Customer Home {); __
Pkic. 8.2. Пример использования языка Persistent State Definition Language
В строке З объявлен abstract storage type для Customer. Кроме того, в строках
5-6 объявляются account Number (только для чтения) и name (для чтения/записи)
с помощью ключевого слова state, сообщающего PSDL-компилятору какие поля
сохранять. Экземпляры Customer создаются фабрикой, поэтому должно
существовать объявление фабрики abstract CustomerHome.
В строках 10-13 объявлен storagehome Customer Ноше. Эта фабрика с
помощью метода create создает объекты Customer, каждый с account Number в
качестве первичного ключа. Разработчик не занимается реализацией порождающего
класса. Программные средства PSS создают код, необходимый для установления
соответствия между хранилищем данных и декларируемым определением
объекта. Производители PSS отвечают за поставку средств согласования указанных
объектов с тем хранилищем данных, в котором эти объекты должны храниться.
394
Глава 8
Два абстрактных объявления (строки 3 и 10) и два конкретных объявления
(строки 25 и 8) представляют собой информацию, которой P8DL-компилятору
достаточно, чтобы создать код, порождающий классы CustomerHomelmpl и Casto-
merlmpl вместе со всеми необходимыми вспомогательными интерфейсами и
абстрактными классами.
8.4.5. Сервисы событий и уведомлений
Сервис событий (Event Service) определяет механизм, отделяющий доставку
событий от их источника. Сервис событий отвечает за отслеживание ActionEvent
и ActionListener, чтобы их могли использовать объекты, которые хотят
отправлять или получать сообщения о событиях. Аналогичным образом объекты Event-
Channel используют тип данных CORBA Any для согласованного распространения
по сети событий любого типа (элементарного или объектного), поскольку
спецификация сервиса событий не содержит предопределенных типов событий. Тем не
менее в Java отсутствует модель распределенных событий, тогда как в CORBA такая
модель есть.
В общем виде поставщик сервиса создает события, которые обрабатываются по
требителем сервиса. В модели проталкивания поставщик сервиса посылает
сообщения о событиях всем потребителям, зарегистрированным на получение
сообщений в асинхронном режиме. В модели опроса потрабитель опрашивает поставщика
о событиях. Если ни одно из них не произошло, потребитель может блокировать
свою реботу до их появления. Неблокируемый клиент, ожидающий появления
событий, должен опрашивать поставщика на регулярной основе. Например, Java
использует модель проталкивания для событий Swing и AWT. Обрабатывающий объект
реализует интерфейс ActionListener и регистрируется у компонента Swing, который
уведомляет обработчик об изменениях состояния компонента пользовательского
интерфейса путем вызова метода обработчика actionPerformed с некоторым Action-
Event. Компонент пользовательского интерфейса посылает сообщение
обрабатывающему объекту асинхронно. Однако события Java отличаются от событий сервиса
событий — сервис событий не определяет тип события. События Java строго
типизированы, в то время как события сервиса событий CORBA могут иметь или
не иметь тип, исходя из типа используемого для передачи сообщений канала.
И поставщики, и потребители сервисов могут иметь любую модель, а типы
используемых ими моделей не обязаны быть согласованы друг с другом. Поставщик
с моделью проталкивания может создавать сообщения для потребителя с моделью
опроса, а потребитель с моделью проталкивания может пассивно ожидать
сообщений от поставщика с моделью опроса. Сервис событий функционирует по типу
очереди (называемой также каналом), в которой события ожидают своих
потребителей. Поставщик может или «проталкивать» события в канел событий или
обеспечивать считывание событий каналом событий. Как только событие оказывается
в очереди, канал проталкивает это событие потребителю с моделью проталкивания
или ждет, когда потребитель с моделью опроса запросит данное событие. В сервисе
событий не существует ни механизма поиска конкретных каналов событий, ни
механизма определения качества обслуживания, но зги важные возможности
добавляются при помощи сервиса уведомлений.
Сервис уведомлений {Notification Service) — это сервис событий
индустриального уровня. Он является непосредственным расширением сервиса событий,
поскольку наследует его исходные IDL-интерфейсы. Объекты могут свободно
создавать или уничтожать каналы событий, а также фильтровать данные этих каналов
с помощью объектов-фильтров (Filter Objects) и грамматики языка объектных
ограничений (Object Constraint Language), первоначально определенного для сервиса
трейдинга и известного как язык трейдерных ограничений [TCL — Trader
CORBA. Часть 2 395
Constraint Language). Сервис уведомлений обеспечивает полную поддержку
сервиса событий. Метод EventServiceHelper.narrow надежно преобразует объектную
ссылку сервиса уведомлений в объектную ссылку сервисе событий. Кроме того,
в сервисе уведомлений определен также тип события, называемый Stmctured-
Event.
На рис. 8.3 изображены уровни непрямого взаимодействия, характерные для
CORBA-cep висов событий /уведомлений, и показано, как автононизапия
поставщиков и потребителей событий увеличивает гибкость, поскольку начинают
действовать дополнительные объекты. Обычная последовательность начинается с того, что
поставщик сервиса ищет объектную ссылку на сервис уведомлений (объект типа
EventChannelFactory). Объект EventChaimelFaetory создает EventChannel, а
поставщик запрашивает у EventChannel объект Supplier Admin. Объект Snpplier-
Admin возвращает один из многих типов посредников Consumer (таких как Stnic-
turedProxyPnshConsnreer), а поставщик использует этот посредник потребителя.
StructuredProxyPushConsunier является посредником того канала событий,
который принимает события StructuredEvent от поставщика. Канал EventChannel,
созданный для Supplier Admin, создает StructuredProxyPushConsunter, так что все
объекты этой цепочки взаимодействуют С одним и тем же каналом событий.
Используя неблокируемый метод модели опроса, поставщик может создавать и
выталкивать в канал события StructuredEvent и готовиться к отправке других событий.
Поставщик посылает сообщение потребителю
Поставщик ы Потребитель
Объект EventChannel автоноыизирует поставщика и потребителя
EvmntChaniMl
■*! Потре!
е1
Добавление РхохуСопяшмх и ProxySuppliex способствует дальнейшей
автономизации и делает возможной поддержку моделей проталкивания и опроса
РгожуСопашмг
EvmntChannel
Рис. 8.3. Канал поставщик-потребитель, использующий сервис событий /у ведом пений
Потребитель с другой стороны делает то же самое. Потребитель ищет ссылку на
сервис уведомлений, приводит объектную ссылку к ссылке на EventChannel-
396
Глава 8
Factory, получает доступ к уже известному нам каналу, получает объект
Consumer Admin, получает S tnicturedP го хуР u sh Supplier, соединяется с
поставщиком сервиса и ждет появления событий.
Конкретным примером применения сервиса уведомлений является
чат-приложение. Чат-объект, посылающий сообщение, является поставщиком. Чат-объект,
получающий сообщение, является потребителем. Обычно чат-приложение
использует модель проталкивания (поставщик чат-сообщения проталкивает сообщения
потребителю чат-сообщений, пассивно ожидающему их). Данному приложению не
нужно использовать специальные IDL-определения, если сообщение передается
с помощью стандартных типов данных CORBA. Если требуются специельные типы
данных (например, структуры или value type TextMessage), разработчик должен
создать соответствующее описание IDL. Чат-приложение, использующее сервис
уведомлений, яаляется примером пирингового приложения — приложения, в
котором клиенты взаимодействуют друг с другом напрямую, без участия
центрального сервера.
8.5. Компоненты EJB и CORBA
Enterprise JavaBeans {EJB) определяет «стандартную компонентную
архитектуру для построения распределенных объектно-ориентированных б
нанес-приложений на языке программирования Java™» [6]. Консорциум OMG разработал CORBA
Component Model (ССМ) Request for Proposal (RFP), в котором компонентная
модель JavaBeans была рекомендована в качестве основы для серверных
приложений. В процессе анализа и планирования серверных приложений на языке Java,
корпорация Sun решила в качестве основы для серверной распределенной
компонентной архитектуры использовать не JavaBeans, a Enterprise JavaBeans (EJB
только по имени похоже на JavaBeans — JavaBeans поддерживает возбуждение
событий, тогда как в EJB не требуется поддерживать события). Компании,
принявшие компонентную модель CORBA, решили придерживаться этой модели и
построили свои компонентные архитектуры, используя идеи EJB, одновременно
извлекая выгоду из расширенных возможностей, уже имеющихся в CORBA.
OMG определяет технологические стандарты для поддержки рынка объектов,
облегчая создание объектно-ориентированных систем. Применение инкапсуляции,
наследования, полиморфизма и динамического связывания делает возможным
повторное использование архитектурных решений. Разработчики ССМ в качестве
основы CORBA-компонентов приняли архитектуру Component Implementation
Framework (CIF). CIF описывает удачные архитектурные шаблоны, базирующиеся на
CORBA и CORBA-технологиях. CIF определяет надмножество языка Persistent State
Definition Language, назызаемого компонентным IDL (CIDL — Component IDL),
компонентную модель CORBA-объектов и модель контейнеров, в которой CORBA-
объекты функционируют во время выполнения.
Чтобы ССМ была возможна, определено несколько дополнений и расширений
CORBA. Понимание Enterprise JavaBeans облегчает понимание этих концепций.
Спецификация ССМ добавляет к IDL ключевые слова, приведенные на рис. 8.4.
Эти ключевые слова позволяют разработчикам создавать высокоуровневые
описания компонентов, допуская наследование от одного класса и нескольких
интерфейсов (для тех из поддерживаемых языков прогреммирования, н которых
существует модель наследования) и поддерживая все обычные атрибуты и синтаксис
операций, имеющиеся в IDL-интерфейсах (включая недавно добавленные
ключевые слова private, public, attribute и т.д.).
CORBA. Часть 2 397
Ключевые слова IDL для поддержки компонентной модели CORBA (CCM)
component
consumes
emit.
finder
getRei.e»
home
import
local
multiple
primaryKey
provides
setftaises
supports
typeld
typePrefix
Рис. 8.4. Ключевые слова IDL. обеспечивающие поддержку CORBA Component Mode! (CCM)
Язык Component Interface Definition Language (CIDL) является расширением
языка Persistent State Definition Language, который был представлен в
разделе 8.4.4. В качестве расширения PSDL, CIDL описывает компоненты способом,
допускающим автоматическое создание п ере и стен т но го кода компонентов. CIDL
описывает также реализацию компонента и управление состоянием. Разработчик
компилирует файл .cidl с помощью CIDL-Компилятора.
Когда создание совокупности компонентов завершено, разработчик объединяет
их в одно целое, снабжая их дескриптором, который описывает способ
использования этих компонентов. Дескриптор является расширением формата описания
открытого программного обеспечения {OSD — Open Software Description Format).
OSD описывает правила установки и дистрибуции программ для конкретного
рабочего места [7]. ССМ описывает версию OSD, модифицированную для поддержки
пакетирования компонентов.
Самыми интересными разделами ССМ являются ее модель программирования
контейнеров и то, как РОА обеспечивает взаимодействие ССМ и EJB.
Коммерческие реализации ССМ ожидаются со дня на день, а в настоящий момент доступна
реализация ОрепССМ Platform университета Universite des Sciences et
Technologies de Lille (LIFL). ОрепССМ является незавершенным продуктом и
распространяется бесплатно.
Класс Container образует иерархию вложения, группируя в одно целое
компоненты и другие контейнеры. Модель программирования с использованием
контейнеров представляет собой среду времени выполнения, в которой реализации
компонентов используют свои контейнеры для доступа к разнообразным сервисам,
которые предусматривает данный контейнер. Контейнеры используют реализацию
сервисов, доступную им посредством среды, которая вводит эти сервисы в действие
(например, сервер приложений). Четыре ключевые аспекта составляют модель
программирования ССМ:
• Внешние типы — интерфейсы, которые видит клиент, имеющий намерение
установить связь с компонентом.
• Контейнерные типы — API, которые использует конкретный компонент для
взаимодействия со своим контейнером во время выполнения.
• Типы контейнерных реализаций — у разных контейнеров разные
взаимосвязи с окружающей средой. Имеется три типа контейнеров CORBA: без
сохранения состояния, диалоговый и долговременный. Каждый контейнерный тип
определяет свои средства связи с системой.
• Категория компонента определяет, к какой общей структуре подходит
компонент. Этот аспект определяют внешние и контейнерные типы.
Три типа контейнерных реализаций мало отличаются друг от друга. Контейнер
без сохранения состояния подразумевает вызов того серванта, на который
ссылается РОА, независимо от того какой идентификатор объекта используется для
реализации вызова. Диалоговый контейнер подразумевает ссылку на промежуточ-
398
Глава 8
ный сервант с конкретным идентификатором объекта, обрабатывающего данную
операцию. Долговременный контейнер подразумевает наличие постоянного
серванта, который отвечает на конкретный идентификатор объекта для обработки
вызовов. Данные контейнерные типы являются непосредственным отражением
некоторого подмножества политик, реализуемых адаптерами РОЛ, поскольку
контейнеры ССМ можно воспринимать как специализированные РОЛ. Адаптерам РОЛ
ставят в соответствие некоторые контейнеры и конфигурируют, исходя из
связанного с ними контейнерного типа.
Контейнер определяет API обеспечения безопасности, долговременного
хранения, транзакций, событий и жизненного цикла компонента (создание, сохранение
и т.д.)- С помощью внутренних интерфейсов контейнера компонент имеет полный
доступ к сервисам, которые тот поддерживает. Чтобы обеспечить связь контейнера
с компонентом, компонент реализует интерфейсы обратного вызова. Контейнеры
могут создавать как временные, так и долговременные компоненты. Любому
компоненту ставится в соответствие только один тип контейнера (без сохранения
состояния, диалоговый или долговременный).
Контейнеры ССМ поддерживают как однопоточные, так и многопоточные
модели (последовательный или параллельный доступ). Контвйвер с последовательным
режимом требует, чтобы все вызовы, обращенные к компоненту, обрабатывались
последовательно. Многопоточный режим уведомляет контейнер о способности
компонента управлять параллельным доступом к своему внутреннему состоянию.
Используемый компонентом дескриптор определяет режнм поточной обработки.
Компоненты могут быть временными ини долговременными. Фабрики
предусматривают пункты создания компонентов с первичными ключами или без них.
Поисковые методы не создают объектов; они просто разыскивают запрашиваемые
объекты, используя некоторые ключевые признаки. Когда клиент какой-либо
фабрики запрашивает некоторый объект, разыскиваемый во время выполнения, то
для завершения вызова экземпляр этого объекта может быть создан. С точки
зрения клиента этот объект был найден в существующем хранилище данных и его ые
нужно было создавать. По логике вещей объект является «созданным* тогда,
когда вызывается метод фабрики create. CIDL определяет фабрики и поисковые
объекты в качестве пунктов создания компонентов, но временные компоненты могут
быть созданы только фабриками, тогда как долговременные компоненты могут
быть созданы фабриками и локализованы поисковыми объектами. Временные
компоненты не имеют первичных ключей (или, по крайней мере, таких
первичных ключей, которые доступны за пределами контейнера, в котором существует
данный компонент), следовательно, у поисковых методов нет ключа, по которому
они могли бы эти объекты разыскивать. Долговременные компоненты, нмея
закрепленные за ними первичные ключи, поддерживают два вида
продолжительности существования: управляамой контейнером и компонентом. Продолжительность
существования, управляемая контейнером, является задачей сопровождения и
администрирования, тогда как продолжительность существования, управляемая
компонентом, является задачей разработчика. На рис. 8.6 приведены типы CORBA-kom-
понентов и их описания [8].
Активация и дезактивация или пассивация являются оконечными
действиями при вызовах операции с объектом. Когда запрос на вызов операции с объектом
поступает брокеру объектных запросов, последний отправляет этот запрос РОА.
РОЛ отправляет вапрос контейнеру, управляющему конкретным компонентом,
а контейнер активирует сам объект. Информация о введении объекта в действие
определяет режим дезактивации этого объекта. Режимами дезактивации
являются режим метода (дезактивация после завершения операции), режим
транзакции (дезактивация после завершения транзакции), режнм компонента
(дезактивация по запросу компонента) или режим контейнера (дезактивация по ус-
CORBA. Часть 2
399
мотрению контейнера). Активация и дезактивация происходят при вызове
компонента контейнером с помощью одного из интерфейсов обратного вызова
этого компонента.
Тип компонента
Service
Session
Entity
Process
Описание
• He поддерживает информацию о состоянии (лишен возможности
сохранять состояние)
• Не имеет уникального идентификатора (первичного ключа)
• Реализует требуемое поведение
• Может использовать транзакции, но не включается в текущую
транзакцию
• Поддерживает информацию о внутреннем состоянии
а Имеет уникальный идеетификэтор, который может использовать
только его контейнер
а Реализует требуемое поведение
а Может использовать транзакции, но не включается в текущую
транзакцию
а Соответствует EJ В-компоненту Session
а Продолжительность существований, управляемая контейнером или
компонентом
• Имеет уникальный идентификатор (первичный ключ)
а Реализует требуемое поведение, которое факультативно может быть
транзакцией ным
• Соответствует EJ В-компоненту Entity
а Управляемая контейнером или компонентом продолжительность
существования, которая недоступна для клиентов
• Управляемая контейнером или компонентом продолжительность
существования первичного ключа компонента, этот первичный ключ
является видимым для пользовательских методов
а Реализует требуемое поведение, которое факультативно может быть
транэакционным
Рис. 8.5. Типы и описание CORBA-компонентов
В распределенных системах клиенты не создают объекты непосредственно; они
ищут нужные им объекты. Процесс поиска осуществляется или с помощью файла,
содержащего IOR данного объекта, или с помощью сервиса именования. Фабрики
создают компоненты или, в терминах ССМ, объекты ComponentHome создают
компоненты. Определение компонента должно также содержать определение
фабрики этого компонента и, если данный компонент является долговременным,
метода find, использующего первичный ключ этого компонента.
Компоненты CORBA вспольауют подмножество сервисов CORRA, относящихся
к Component Implementation Framework (среде реализаций компонентов). Эти
сервисы включают сервисы безопасности, транзакций, устойчивых состояний и
уведомлений.
Сервис безопасности определяет схему авторизации, основанную на ролях.
У каждого компонента могут быть свои требования к безопасности (определенные
в дескрипторе компонента), а их соблюдение лежит на контейнере. Бели
контейнер обновляет схему обеспечения безопасности компонента, то новый режим
действует до тех пор, пока не будет вызван другой компонент и пока не будет введен
400
Глава 8
в действие режим обеспечения безопасности этого компонента. Стандартная
спецификация сервиса безопасности с целью поддержки этих требований не изменялась.
ССМ допускает применение упрощенной версии сервиса объектных
транзакций. В спецификации сервиса устойчивых состояний определена упрощенная
версия OTS, чтобы дать возможность использовать упрощенные реализации OTS
(например, такую, которая позволяет базовому хранилищу данных самому управлять
всеми операциями доступа, связанными с транзакциями). Интерфейсы OTS ни на
что не влияют, то есть замена базового OTS не требует изменения кода,
использующего данный OTS. Кроме того, границами (началом и завершением) транзакций
могут управлять как контейнер, так и сам компонент. Примером границы
транзакции является момент активации /дезактивации.
Сервис устойчивых состояний управляет временем существования объекта.
При продолжительности существования, управляемой контейнером,
использование PSS прозрачно для компонента. Модель продолжительности существования,
управляемая контейнером, может показаться наиболее подходящей, но не
обязательно идеально подходит для любой архитектуры распределенных приложений.
Компоненты, самостоятельно управляющие продолжительностью своего
существования, также используют PSS для сохранения своих состояний, но это
происходит за счет последующего сопровождения.
ССМ-компоненты получают доступ к сервису событий опосредствованно в
зависимости от того, какое ключевое слово IDL они используют: publishes или emits.
Контейнер, в котором функционирует компонент, является посредником в
использовании этим компонентом сервиса событий. Информационные параметры
настройки качества обслуживания, требующиеся конкретному компоненту, также
устанавливаются контейнером. Сервисы уведомлений и событий, поддерживаемые
данным контейнером, должны поддерживать только подмножества сервиса
уведомлений и событий, определенных спецификацией ССМ. Использование
сгенерированного компонентного API не мешает непосредственному использованию
сервиса уведомлений, но разработчики компонентов предпочитают определять
события в IDL в качестве способа сохранения функционального описания компонента
в одном месте.
Действие ключевых слов publishes и emits похоже. Отличие заключается в
количестве потребителей, которым разрешено получать сообщения, посылаемые
компонентами. При использовании компонентом в событии ключевого слова
publishes на него может подписаться произвольное число потребителей, тогда как
на событие, в котором компонент использует ключевое слово emits, может
подписаться только один потребитель. Предполагается, что события, объявленные
с ключевым словом publishes, являются частью совместно используемого
интерфейса данного компонента (API, доступный всем клиентам). Предполагается, что
канал событий, созданный с ключевым словом emits, является приватным
каналом, используемым внутри системы. На рис. 8.6 объявлен компонент Customer,
который публикует событие PropertyEvent с ключевым словом publishes для
использования несколькими потребителями.
Компоненты, как и другие CORBA-объекты, могут взаимодействовать с
сервисами CORBA. Для поиска существующих сервисов компоненты используют
брокеры объектных запросов. Процесс поиска подразумевает использование сервиса
именования, однако ССМ предлагает для компонентов сервис HomeFinder для
поиска объектов Component Home других компонентов. HomeFinder является
разновидностью сервиса именования, который используется строго для поиска объектов
ComponentHome.
ССМ определяет основу для реализации распределенных объектов уровня
предприятия с помощью CORBA. Инфраструктура ССМ делает возможным
использование подмножества сервисов CORBA (именования, безопасности, транзакций, ус-
CORBA. Часть 2
401
тойчивых состояний и событий). Фабрики компонентов, определенные на IDL,
являются производными классами Component Но те. Эти фабрики компонентов
находят с помощью объектов HomeFinder — это еще одно применение сервиса
именования. Компоненты ССМ бывают четырех типов: сервис, сеанс, сущность
и процесс. Исходя из количества объявленных компонентами состояний и в
зависимости от того, нуждаются ли эти состояния в сохранении, компоненты могут
делегировать сохранение состояния контейнеру, в котором они расположены (время
существования, управляемое контейнером) или могут сами управлять
сохранением состояния (время существования, управляемое компонентом). Контейнеры
ССМ могут быть однопоточными или многопоточными в зависимости от того,
может ли компонент ССМ управлять одновременным доступом. Клиенты получают
доступ к компонентам при посредничестве контейнера, в котором эти компоненты
в ыпо л няются.
1 // наш компонент Customer кокет отправлять два сообщения:
2 // creditEveot, если лимит данного клиента исчерпан,
3 // и internalstate£vent, если какие-то данные клиента
4 // били обновлены неправильно
5 component Customer (
6 publishes PropertyEvent creditEvent;
7 emits InvariалtEveot internalStateEvent;
S };
Рис. 8.6. IDL-описание компонента Customer, демонстрирующее использование
ключевых слов publishes и emits при создании событий
Enterprise JavaBeans (см. третью часть книги) и компоненты C0RBA в
значительной мере имеют одно и то же происхождение. Сутью замысла в спецификации
ССМ является CORBA-модель компонентного программирования (и сервер
приложений, и EJB-сервер работают в пространстве процессов среды времени
выполнения). Спецификация EJB указывает, что среда времени выполнения должна
поддерживать сервисы именования, безопасности, устойчивых состояний и
транзакций. Структура распределенных объектов EJB использует RMI в качестве клея
и на стороне клиента, и на стороне сервера, определяет EJBHome в качестве
базового интерфейса фабрик компонентов, a EJBObject в качестве базового интерфейса
компонентов. Существуют EJB двух типов: сеансовые (без сохранения и с
сохранением состояния) и сущностные (с сохранением состояния). Не сохраняющие
состояние сеансовые компоненты никогда не бывают долговременными,
сохраняющие состояния сеансовые компоненты в зависимости от режима контейнера могут
быть долговременными. Сущностные компоненты всегда являются
долговременными. Компоненты EJB должны быть однопоточными; их контейнеры не
специфицированы как многопоточные. Клиенты получают доступ к компонентам EJB
е помощью EJB-контейнера [9].
Модели ССМ и EJB довольно похожи. Спецификация ССМ определяет два
уровня компонентов: базовый и расширенный. Базовый компонент является
практически полным подобием модели EJB. Базовый компонент помещается в контейнер,
поддерживающий только однопоточный режим работы; использует только
сервисы безопасности, транзакций и устойчивых состояний; он предоставляет клиентам
только один интерфейс. Расширенный компонент поддерживает множественные
интерфейсы. ССМ определяет усовершенствованные типы хранилищ и
предоставляет возможность корректного долговременного хранения. Расширенный
компонент поддерживает также использование сервисов событий.
Корпорация Sun разработала Enterprise JavaBeans независимо от архитектуры
компонентов C0RBA. Тем не менее OMG и Sun тесно сотрудничали с целью объеди-
402
Глава 8
нения этих двух архитектур в процессе работы над ССМ, теперь спецификация
Enterprise JavaBeans официально входит в архитектуру ССМ. Реализация EJB,
построенная поверх ССМ, будет гораздо более расширяемой системой, чем система на
основе EJB, построенная с нуля. Спецификация EJB версии 2.0 требует
поддержки ПОР. Производители, поставляющие продукты, совместимые EJB 2.0, должны
поддерживать CORBA. Поддержка ПОР не означает, что EJB-контейнеры могут
работать с компонентами ССМ. Разработчики могут писать компоненты ССМ на
любом языке программирования, для которого существуют преобразования CORBA
ССМ. В результате EJB — ето первое реальное внедрение архитектуры
распределенных объектов (а не просто распределенных объектов самих по себе).
Интеграция систем вступает в зрелую стадию развития, когда COBOL-компоненты смогут
непосредственно взаммодействовать с Java-компонентами или C++-компонентам и
в программно реализованных средах, использующих аналогичные принципы
и API-интерфейсы.
8.6. CORBA и RMI
CORBA представляет собой всеобъемлющую концепцию архитектуры
распределенных систем, тогда как RMI всего лишь описывает модули доступа и протоколы
взаимодействия между клиентским и серверным объектами. Спецификация ОМА
определяет пути интеграции систем, использующих концепции объектной
технологии и системной архитектуры. CORBA следует спецификациям ОМА, в ней
нашел отражения практический опыт создания распределенных систем.
Организации-члены OMG сумели сделать CORBA полезной и надежной технологией.
8.6.1. Когда использовать RMI
RMI подходит для небольших распределенных приложений, для которых
масштабируемость, гетерогенность среды и расширяемость имеют не слишком
большое значение. Какое-то время у RM1 не было долгосрочной перспективы развития,
такой, какой была ОМА для CORBA. С архитектурной точки зрения построение
инфраструктуры для RMI-системы — непростая задача. Привязка RMI и его
возможностей к ОМА осуществима только для объектов-приложений и брокеров
объектных запросов. Объекты-приложения являются специфичными для данного
приложения или системы и, следовательно, не имеют официально признанных
спецификаций. Коммуникационная инфраструктура RMI имитирует брокер
объектных запросов (с реестром RMI, используемым в качестве сервиса именования).
Кроме отсутствия стандартной архитектуры, например, определяемой сервисами
и средствами CORBA, RMI не предоставляет ни средств обеспечения качества
обслуживания, ни асинхронных вызовов. В RMI нет эквивалента РОА и тех
возможностей настройки, которые дает его применение. Кроме того, автоматическая и
динамическая загрузка классов в RMI позволяет клиенту выполнять некоторые
операции локально, а не через сетевые вызовы. В CORBA есть ключевое слово struct,
но его применение переносит бремя реализации и связывания на клиента, что для
некоторых приложений является неоптимальным.
Enterprise JavaBeans, с другой стороны, представляют собой более общий
подход к системам и используются там, где необходимо создание крупномасштабных
систем. Во многих системах обработка и безопасность данных являются
факторами высокого риска, требующими применения специализированных подсистем для
снижения этих рисков. Эти подсистемы, например, безопасности и устойчивых
состояний, а также транзакций и обмена сообщениями появляются вновь и вновь
в качестве постоянных составных частей разрабатываемых систем. До появления
CORBA. Часть 2
403
CORBA системные интеграторы создавали специализированные инфраструктуры
для осуществления надежной связи между гетерогенными системами и
стандартными сервисами. После того как CORBA и RMI привели к стандартизации
коммуникационной инфраструктуры, прикладные системы стали строиться на их
основе. Enterprise JavaBeans является надежным и рентабельным способом построения
больших распределенных систем. EJB представляет собой Java-реализацию
инфраструктуры первого поколения, непосредственно ведущей свое происхождение
от CORBA.
RMI дает объектам возможность согласованно и предсказуемо участвовать в
работе распределенных систем. RMI — естественный выбор для распределенных
систем, разрабатываемых на Java. RMI не подходит для некоторых системных
конфигураций, которые требуют контейнерных сервисов, выравнивания нагрузки и
другой базовой функциональности без дополнительной разработки. Архитектура
распределенной системы, использующей только возможности Java, должна быть
продумана заранее, причем проблемы, связанные с распределенными объектами,
должны быть выявлены и решены до того, как будут решены проблемы,
связанные с реализацией. Вопрос о том, где использовать RMI, лучше
переформулировать, какие задачи проектирования лучше решать с помощью RMI?
8.6.2. Когда использовать CORBA
Если разработчики, не считая небольших проектов, не должны использовать
RMI, это ставит IDL в неудобное положение. IDL изучить нетрудно, но эта задача не
из легких для разработчиков, незнакомых с синтаксисом C/C++. CORBA может
оказаться слишком высоким порогом в обучении для тех, кто только начинает
изучение объектных технологий. Тем не менее, CORBA по-прежнему остается
прекрасным способом разработки всего спектра распределенных систем, даже без такой
базы, как компоненты CORBA. Причина, по которой CORBA представляет собой
хороший выбор при проектировании сложных систем, относится к концепциям
реализации (например, ОМА). В том, что касается возможностей соединения, и RMI,
и CORBA используют один и тот же протокол (ПОР), но RMI, по сравнению
с CORBA, уступает во многих других областях.
• Архитектура. RMI никак не определяет архитектуру. В CORBA
общепризнанная архитектура распределенных систем была определена уже в начале
80-х годов прошлого века. Переход к архитектурам распределенных
компонентов, типа Enterprise JavaBeans, всегда присутствовал в основных планах.
Апробированная архитектура, такая как EJB (использующав механизмы
подобные RMI), помогала OMG достигнуть высокой степени стандартизации.
• Качество обслуживания (QoS). Спецификация QoS устанавливает политики
для распределенного объекта на многих уровнях. Пока еще не существует
программных концепций QoS в Java, RMI или EJB.
• Масштабируемость. RMI поддерживает концепцию объектного адаптера, но
только в качестве базового механизма для скрытия сложности вызова
методов. Концепция переносимых объектных адаптеров {РОА}, поддерживающая
различные политики активизации, которые можно выбрать, включая ту,
которая дает возможность одному серванту обрабатывать вызовы разных типов
объектов, неизвестна в RMI.
• Гетерогенность. Java, как язык реализации, может работать с различными
операционными системами и на нескольких аппаратных платформах. Такой
подход отличается от подхода CORBA к гетерогенности: CORBA предоставляет
системам, написанным на разных языках программирования для различных
операционных систем и аппаратных платформ, возможность взаимодействия.
404
Глава 8
CORBA побуждает производителей программного обеспечения переносить
технологию COREA на различные платформы и языки программирования.
• Расширяемость. Независимость от аппаратуры, от языка программирования
и прозрачность удерживают CORBA на переднем крае сложных проектов
системной интеграции.
8.6.3. RMI-IIOP
Объединенными усилиями Sun и ШМ реализовали RMI поверх ПОР, (RMI-IIOP —
RMI over ПОР) для замены лежащего в основе RMI коммуникационного
протокола {Java Remote Method Protocol или JRMP) на CORBA ПОР. Borland. Netscape
и Oracle, работал с Sun и ШМ, специфицировали обратное преобразование из Java на
IDL (Java-IDL), позволяющее RMI-компилятору (rmic) создавать IDL-файлы,
заглушки и скелеты, нужные серверу, чтобы принимать вызовы, и клиенту, чтобы делать
вызовы. При обратном преобразовании действуют следующие ограничения [10]:
1. Константы могут быть только простых типов или строками.
2. IDL не поддерживает перегрузку имен методов.
3. Класс не может наследовать метод с одной и той же сигнатурой от двух
разных интерфейсов.
4. Интерфейсы и типы значений должны быть открытыми.
5. Компилятор воспринимает имена пакетов и интерфейсов как идентичные,
если единственным отличием является зависимость от регистра.
Это преобразование устанавливает также четыре ограничения времени
выполнения [11]:
1. Передача древовидного графа (объекта, содержащего ссылки на другие
объекты) от брокера к брокеру объектных запросов может быть
проблематичной, если многие узлы указывают на один объект (вместо них возможно
будут отправлены копии).
2. CORBA не поддерживает распределенную сборку мусора.
3. Приведение типов для заглушек может работать неправильно, поэтому
поощряется использование статического метода narrow класса java.rmi.Por-
tableRemoteObject. RMI загружает заглушки, нужные данному клиенту
для взаимодействия с сервером, однако CORBA не поддерживает такого
режима функционирования.
Разработка распределенного приложения с использованием RMI-IIOP включает
обычные для RMI этапы разработки (см. главу 2) с небольшими изменениями:
1. Используйте javax.rml.PortableRemoteObject вместо java.rmi .UnicastRe-
mo teObject. Обходной маневр — использовать valuetypes (рассматривался
в главе 7).
2. Используйте интерфейс каталогов и именования Java (JNDI — Java
Naming and Directory Interface) вместо реестра RMI.
3. He приводите удаленные объекты к типам производных классов;
используйте метод narrow класса PortableRemoteObject для приведения
распределенных объектов к типам производных классов.
CORBA. Часть 2
405
8.7. Комплексный пример приложения.
RMIMessenger с использованием RMI-IIOP
В этом разделе мы модифицируем программу RMI messenger из главы 2 с целью
использования RMI-IIOP. Перенести RMI Messenger на RMI-ПОР проще, чем на
CORBA (глава 7). Удаленные интерфейсы (ChatServer, StoppableChatServer и Chat-
Client) остаются теми же. Модульный принцип, который мы применили при
разработке системы Deitel Messenger, позволяет нам повторно использовать
вспомогательные классы и интерфейсы (ChatMessage, ClientGUI, Message Listener. Message-
Manager и DisconnectListener) также без изменения.
Для RMI-IIOP Messenger мы предусмотрели новые реализации удаленного
интерфейса ChatServer (ChatServerlmpI), Chat Server Administrator, интерфейса Mes-
BageManager (RMIIIOPMessageManager) а модули запуска клиента
(Deite(Messenger).
8.7.1. ChatServer, реализованный с применением RMI-IIOP
Класс ChatScrverlmpl (рис. 8.7) реализует удаленный интерфейс ChatServer
в качестве производного от класса j avas .rmi. Port a hie Remote Object (строка 20),
который является базовым классом для удаленных объектов RMI-IIOP. В
удаленных интерфейсах ChatServer и StoppableChat Server, которые реализует класс
ChatScrverlmpl (строка 21), не требуется никаких изменений. Класс
ChatServerlmpI не реализует метод register, который регистрирован Activatable RMI
ChatServer в реестре RMI. Вместо этого версия RMI-IIOP класса Chat Server
Administrator (рис. 8.8) упрааляет регистрацией в сервисах именования. Остальная
часть RMI-ПОР ChatServerlmpI идентична своему RMI-прототипу.
1 // ChatServerlmpI.Java
2 // ChatServerlmpI реализует удаленные интерфейсы ChatServer и
3 // StoppableChatServer с использованием RHX поверх ПОР.
4 package cam.deitel.messenger.rrai_iiop.server;
5
6 // Базовые пакеты Java
7 import Java. io.*;
8 import java.util.*;
9 import java.net.HalformedURLException;
10 import Java.rmi.*;
11
12 // Пакеты расширения Java
13 import javax.rmi.*;
14
15 // Пакеты Deitel
16 import com.deitel.messenger.rmi.ChatMessage;
17 import com.deitel.messenger.rmi.client.ChatClient;
18 import com.deitel.messenger.rmi.server.*;
19
20 public class ChatServerlmpI extends PoxtableRemoteObject
21 implements ChatServer, StoppableChatServer {
22
23 // Список ссылок ChatClient
24 private Set clients = new HashSetf);
25
26 // создаем новый ChatServerlmpI
27 public ChatServerlmpI() throws RemoteException
28 {
// регистрируем новый ChatClient у ChatServer
public void registerClient( ChatClient client >
throws RenoteException
t
// добавляем клиента, в список зарегистрированных клиентов
synchronized( clients ) {
clients.*dd( client };
System.out.println( "Registered Client: " + client );
] // завершение метода registerClient
// отменяем регистрации клиента у ChatServer
public void unregisterClient{ ChatClient client )
throws RemoteException
t
// удаляем клиента из списка зарегистрированных клиентов
synchronized{ clients ) {
clients.remove{ client );
>
System.out-println{ "Unregistered Client: " + client >;
} // завершение метода unregisterClient
// отправляем новое сообщение ChatServer
public void postMeaaage( ChatMessage message )
throws RemoteException
t
Iterator iterator = null;
// получаем итератор для списка зарегистрированных клиентов
synchronized( clients ) {
iterator = new HashSet{ clients ).iterator();
>
// отправляем сообщение каждому ChatClient
while { iterator.hasNextO ) {
ChatClient client = ( ChatClient ) iterator.next{);
client.deliverMessagef message );
>
} // завершение метола poatMessage
// уведомляем каждого клиента о завершении работы
// сервера и завершаем работу серверного приложения
public void stopServer<) throws RemoteException
t
System.out.println{ "Terminating server ..." >;
Iterator iterator = null,-
// получани итератор для списка зарегистрированных клиентов
synchronized( clients ) (
CORBA. Часть 2 407
87 iterator = пен Ha*hSet{ clients ).iterator();
88 1
89
90 // посылаем сообщение serverstopping каждому ChatClient
91 while { iterator.hasNext{) ) {
92 ChatClient client = { ChatClient ) iterator.next{);
93 client.serverstopping <);
94 System.err.printing "Disconnected: " + client );
95 >
96
97 // создаем поток для завершения приложения после
98 // завершения метода stopServer
99 Thread terminator = new Thread(
100 new Runnable{) {
101
102 // ждем 5 секунд, выводим сообщение и завершаем работу
103 public void run{)
104 {
105 // ждем
106 try {
107 Thread.sleep( 5000 );
108 )
109
110 // игнорируем interruptedExceptions
111 catch { InterruptedSxception exception ) {}
112
113 System.err.println< "Server terminated" );
114 System.exitf 0 >;
115 1
116 )
117 > ;
118
119 terminator.start(); // запускаем поток завержения
120
121 } // завершение метода stopServer
122 )
Рис. 8.7. ChatServerlmpI реализует ChatServer с помощью RMI-IIOP
Класс ChatServerAdministrator (рис. 8.8} представляет собой вспомогательную
программу для запуска и останова RMI-IIOP-реализации ChatServer. Метод start-
Server (строки 21-53} создает экземпляр ChatServerlmpI (строка 27} и
регистрирует этот ChatServer в сервисе именования (строки 30-85}. Эквивалентом реестра
RMI для RMI-ПОР является сервис именования CORBA. Вместо подключения
к сервису именования способом, принятым в CORBA, Java рекомендует
использовать Java Naming and Directory Interface (JNDI), который абстрагирует
концепцию сервисов именования и каталогов. Пакет javax.naming {строка 13} содержит
классы и интерфейсы JNDI. Строка 30 создает InitialContext, который
представляет сервис именования. В строке 35 осуществляется вызов метода rebind
интерфейса Context для связывания в сервисе именования объекта ChatServerlmpI
с именем "ChatServer", делая ChatServer доступным для клиентов. Во время
выполнения InitialContext использует два системных свойства для поиска базового
сервиса именования (в документации Java для javax.naming.InitialContext и ja-
vax. naming .Context заданы подходящие значения свойств по умолчанию}.
Свойство j a va.naming.factory. initial определяет имя класса фабрики, которая создаст
объект InitialContext. Класс com.sun.jndi.cosnaming.CNCtxFactory является
классом фабрики по умолчанию при подключении к сервису именования CORBA.
Свойство Java.naming.provider.url задает унифицированный указатель ресурса
сервиса именования, включая порт, к которому подключен данный сервис
именования (например, iiop://localhost:1050).
1 // ChatServerAdministrator.Java
2 // ChatServerAdministratoг является утилитой для запуска
3 // и останова реализации RHI-IIOP ChatServer.
4 package com.deitel.messenger.rmi_iiop.server;
5
6 // Базовые пакеты Java
7 import Java.rmi.*;
в import Java.rmi.activation.*;
9 import java.util.*;
10
11 // Пакеты расширения Java
12 import javax.rmi.*;
13 import javax.naming.*;
14
15 // Пакеты Deitel
16 import com.deitel.messenger.rmi.server.StoppableChatServer;
17
10 public class ChatServerAdministrator {
19
20 // создает объект ChatServer
21 private static void startServer()
22 {
23 // регистрируем ChatServer в сервисе именования
24 try {
25
26 // создаем объект ChatServerlmpl
27 ChatServerlmpl ChatServerlmpl = new ChatServerlmpl();
28
29 // создаем XnitialContext для сершиса именования
30 Context namingContext = new XnitialContext{);
31
32 System.err.printLn( "Binding server to naming service.." );
i объект ChatServerlmpl с сервисом имеяовани
namingContext.rebind( "ChatServer", ChatServerlmpl );
System.out.printing "Server bound to пни!пд service" );
) // вавершение блока try
// обрабатываем исключения при регистрации ChatServer
catch { NamingException namingException ) {
namingException.printStackTrace{) '•
System.exit{ 1 ) ,-
)
// обрабатываем исключения при создании ChatServer
catch ( HemoteExceptioo remote&xception ) |
remoteException.printStackTrace{);
Syate».exit{ 1 );
}
метода startServer
CORBA. Часть 2
// завершаем работу сервера
private static void terminateServer()
{
// ищем ChatServer и завершаем его работу
try {
// создаем контекст именования для помеха сервера
Context namingContext = пек InitialContext{);
// идем удавениый объект ChatServer
Object remoteObject =
namingContext.lookup( "ChatServer" );
// приводим remoteObject x StoppableChatSarvex
StoppableChatServer ChatServer =
( StoppableChatServer ) PortableRemoteObject.narrow(
remoteObject, StoppableChatServer.class );
// останавливаем ChatServer
ChatServer.stopServer{);
// удаляем ChatServer из сервиса именования
namingContext.unbind( "ChatServer" );
// обрабатываем исключения при поиске ChatServer
catch { NamingException namlngException ) {
naming£xception.printStacXTrace{);
// обрабатываем исключения при аваимодействни с ChatServer
catch { RemoteException remoteException ) (
remoteException.printStackTrace{);
метода terminateServer
92
93 // запускаем приложение ChatServerAdministrator
94 public static void main( String args[) )
95 (
96 // проверяем параметры командной строки,
// запускаем ния останавливаем сервер
97 if { args[ 0 ).equals( "start" ) )
98 atartServer();
99
100 else if { arg*[ 0 ).equals{ "stop" ) )
101 terminateServer();
102
103 // выводим информацию об использовании
104 else
105 System.err.println{
106 "Usage: Java ChatServerAdministrator start I stop"
107
108 ) // завершение метода main
109 )
Рис. 8.8. Приложение ChatServerAdministrator для запуска и останова ChatServer при
использовании RMI-I10P (часть 1)
Рис. 8.8. Приложение ChatServerAdmlnJstrator для запуска и останова ChatServer при
использовании RMI-MOP (часть 2)
CORBA. Часть 2
411
Метод terminate Server (строки 56-91) находит ChatServer, завершает его
работу и отменяет регистрацию ChatServer в сервисе именования. В строке 62
создается InitialContext для сервиса именования, в котором зарегистрировав ChatServer.
В строках 65—66 вызывается метод lookup, ему передается в качестве параметра то
имя, под которым зарегистрирован ChatServer. Метод lookup возвращает ссылку
на удаленный объект ChatServer. В строках 69-71 создается ссылка Stoppable-
Chat Server на удаленный объект ChatServer. Напомним, что RM[-[[OP требует,
чтобы удаленные объекты были преобразованы к определенным типам с помощью
метода narrow класса PortableRerooteObject. Метод narrow принимает в качестве
параметров ссылку Object на удаленный объект и объект Class в качестве целевого
ссылочного типа. В строке 71 осуществляется передача rerooteObjeci и объекта
Class для интерфейса StoppableCbatServer методу narrow. В строке 74
вызывается метод stop Server интерфейса StoppableChatServer для завершения
ChatServer. В строке 77 вызывается метод unbind интерфейса Context для отмены
регистрации ChatServer в сервисе именования. В строках 82-84 осуществляется
перехват исключения NamingBxception, если ChatServer не найден в сервисе
именования. В строках 87-98 перехватывается исключение RemoteException, если
возникла проблема с подключением к удаленному объекту ChatServer или С вызовом
его удаленных методов.
На рнс. 8.8 показаны результаты работы Chat Server Administrator. Здесь
CbatServerAdministrator запускает ChatServer и связывает его с сервисом
именования. Три клиента подключены к сервису именования, первый клиент затем
отключается. CbatServerAdministrator останавливает ChatServer, который
отключает двух оставшихся клиентов и завершает работу сервера.
8.7.2. Реализация ChatCIient с применением RMI-IIOP
Чтобы реализовать ChatCIient для RMI-IIOP Messenger, мы должны обеспечить
новую реализацию интерфейса MessageManager с использованием RMl-HOP-
Затем мы должны создать новое приложение Dei t el Messenger для запуска Client GUI
с новой реализацией MessageManager.
Класс RMlIIOPMessageManager (рис. 8.9) реализует интерфейсы ChatCIient
и MessageManager с помощью RMI-IIOP. Как и реализация RMIMessageManager
из главы 2, класс RMlIIOPMessageManager представляет собой удаленный
объект, который подключается к удаленному объекту ChatServer. Однако класс
RMlIIOPMessageManager расширяет класс PortableRerooteObject для
совместимости с ПОР (строка 24). Метод connect (строки 38-57) создает InitialContext для
сервиса именования (строка 42) н вызывает метод lookup интерфейса Context для
получения удаленной ссылки на RMI-IIOP ChatServer (строки 45-46). В строках
48-49 вызывается метод narrow класса PortableRerooteObject для преобразования
удаленной ссылки Object в удаленную ссылку ChatServer. В строке 52 вызывается
метод register Client интерфейса ChatServer для регистрации
RMlIIOPMessageManager в качестве клиента ChatServer. В строке 55 задается, какому слушателю
Message Listener должны доставляться входящие сообщения ChatMesBage.
Остальная часть класса RMlIIOPMessageManager идентична классу RMIMessage-
Manager.
1 // RMIIIOPMaeeageManager.Java
2 // RHHIOPM22esaageHanager реализует удаминый интерфейс
3 // ChatCIient и управляет входящими и исходящими
4 // сообщениями, используя BMI поаерх ПОР.
5 package con.deitel.messenger.rroi_iiop.client;
6
7 // Базовые пакеты Java
8 import java.awt.*;
9 import java.awt.event.*;
10 iroport Java.rmi.*;
11 iroport Java.rmi.server.*;
12 iroport java.util.*;
13
14 // Пакеты расширения Java
15 import javax.rmi .*;
16 import javax.naming.*;
17
18 // Пакеты Deitel
19 import com.deitel.messenger.*;
20 import com.deitel.messenger.rmi.client.ChatClient;
21 import com.deitel.messenger,rmi.ChatHessage;
22 import com.deitel.messenger,rmi.server.ChatServer;
23
24 public class RMXIIOPMessageManager extends PortableRemoteObject
25 implements ChatClient, HessageManager {
26
27 // слуитвли входящих сообщений и уведомлений об отключении
28 private HessageListener messageListener;
29 private DisconnectListener disconnectListener;
30
31 // ChatServer для отправки и получении сообщений
32 private ChatServer ChatServer;
33
34 // конструктор KHlMessageManager
35 public RMXllOPMessageManager{) throws RemoteException {)
36
37 // подключаем к ChatServer
38 public void connect( HessageListener listener )
39 throws Exception
40 {
41 // создаем контекст иш
42 Context namingContext =
43
44 // ищем улаженный об"ьект ChatServer
45 Object remoteObject =
4 6 namingContext.lookup( "ChatServer" );
47
48 ChatServer = ( ChatServer ) PortableRemoteObject.narrow(
49 remoteObject, ChatServer.class );
50
51 // регистрируем клиент на ChatServer для получения сообщений
52 ChatServer.registerClient( this );
53
54 // задаем слуваталь для приема входящих сообщений
55 messageListener = listener;
) // завершение метода
// отключаем от ChatServer
public void disconnect( MessageListener listener )
throws Exception
i
if ( ChatServer = null )
return;
CORBA. Часть 2
ChatServer.unregisterClient( this );
messageListener = null;
// уведомляем слушатель об
fireServerDisconnected( ""
) // завершение нетода di:
// посылаем ChatMessage серверу ChatServeг
public void sendMessage{ String fromUser, String message )
throws Exception
(
if ( chatServer = null )
81 // создаем ChatMessage с текстом сообщение и именем пользователя
82 ChatMessage ChatMessage =
83 new ChatMessage{ fromUser, message );
84
85 // отправляем сообщение серверу ChatServer
86 ch&tServer.postMessage{ ChatMessage );
87
88 ) // завершение нетода sendMessage
89
90 // обрабатываем сообщения, полученные от ChatServer
91 public void deliverMessage( ChatMessage message )
92 throws RemoteException
93 <
94 if ( messageListener ' = null )
95 messageListener.messageReceived( message.getSender{),
96 message.getMessage() );
9' }
98
99 // обрабатываем уведомление о завершении работы сервера
100 public void server-topping() throws RemoteException
101 (
102 chatServer = null;
103 fireServerDisconnected( "Server shut down." );
104 )
105
106 // регистрируем слуиатель для получения уведомлений об отключении
107 public void setDisconnectListener(
108 DisconnectListener listener )
109 (
110 disconnectListener = listener;
111 }
112
113 // отправляем уведомление об отключении
114 private void fireServarDisconnected( String message )
115 (
116 if { disconnectListener != null )
117 disconnectListener.serverDisconnectedf message );
118 )
119 )
Рис. 8.9. RMIIIOPMessageManager реализует интерфейсы ChatClient и Message Manager
с помощью RMI-IIOP
Класс DeitelMessenger (рис. 8.10) запускает клиент Deitel Messenger с
помощью классов ClientGUI и RMIIIOPMessageManager. В строке 18 создается
экземпляр класса RM1 ПО PMessage Manager для обмена сообщениями с Chat Server.
В строках 21-24 создаются ClientGUI для RMniOPMessageManager и
отображается графический интерфейс пользователя.
1 // DeitelMessenger.java
2 // DeitelMessenger использует ClientGUI и RMIIIOFHessageManager
3 // длк реализации чат-клиента с помощью RHI поверх ПОР.
4 package com.deitel.messenger.rmi_iiop.client;
5
6 11 Базовые
7 import java
8
9 // Пакеты Deitel
10 import com.deitel.messenger.*;
11
12 public class DeitelMessenger {
13
14 // запускаем приложение DeitelMessenger
15 public static void mein( String axgsll ) throws Exception
16 {
П II создаем RMlIIOPMessageHanager для обмена сообщениями
//с сервером
18 MessageHanager messageManagar = пек RMlllOPMessageManager();
19
20 // настраиваем и отображаем окно чата
21 ClientGUI ClientGUI = new ClientGUI( me&aageHanager );
22 ClientGUI.setSize( 300, 400 );
23 ClientGUI.setResisable( false );
24 ClientGUI.setvisible{ true );
Рис. 8.10. DeitelMessenger создает ClientGUI <л RMIIIOPMessageManager для запуска
клиента RMI-IIOP Messenger
8.7.3. Компиляция и выполнение ChatServer и ChatClient
Скомпилируйте ChatServerlmpI, ChatServerAdministrator,
RMIIIOPMessageManager и DeitelMessenger с помощью компилятора Java. Вы должны также
скомпилировать классы заглушек для удаленных объектов ChatServerlmpI
и RMIUOPMeseageManager. Используйте утилиту rmic с опцией командной
строки -Пор для генерации соответствующих заглушек, например,
rmic -iiop com.deitel.messenger.rmi_iiop.server.ChatServerlmpI
Выполнение версии RMI-НОР Deitel Messenger требует сервиса именования
CORBA. Для этого примера мы используем tuameserv, который ранее
использовался в примерах главы 7. Запустите tnameserv, введя в командной строке
следующее:
tnameserv -ORBInitialPort 1050
Класс ChatServerAdministrator создает ChatServer и регистрирует сервер
в сервисе именования. Для запуска ChatServer наберите в командной строке
команду:
CORBA. Часть 2
415
Java -DJava.naming.factory.initial»
com.sun.jndi.cosnaming.CNCtxFactory
-Djava.naming.provider.url=iiop://localhoet:1050
com. deitel.messenger.rmi_iiop.*erv«i.ChatSarvarAdministrator
start
Опция командной строки -D задает системные параметры виртуальной машины.
Напомним, что свойство java.naming.factory.initial определяет класс,
реализующий InitialContext сервиса именования. Свойство Java .naming, provider, uri
определяет адрес и порт, на которых функционирует сервис именования. В данном
примере сервис именования работает на локальной машине, порт 1050.
После того как ChatServer начал работать и зарегистрирован в сервисе
именования, запустите нескольких клиентов, введя в командной строке следующую
команду и выполнив ее несколько раз:
Java -D]ava.naming,factory.initial=
com.sun.jndi.cosnaming. CNCtxFactory
-Djava.naming.provider.url=iiop://localhost:1050
com.deitel.messenger.rmi_iiop.client.DeitelMassenger
8.8. Пути развития
Архитектура управления объектами (ОМА — Object Management Architecture)
определила уровни объектных сервисов в первой полной версии архитектуры
C0RBA в 90-ых годах прошлого века. ОМА является руководящим документом
при проектировании многих CORBA-систем, но пока еще не является
эффективной при создании системам средних размеров, нуждающихся в распределенных
объектах. CORBA и ОМА подразумевают, что разработчики используют гибкость
эталонной архитектуры для достижения эффективных решений для каждой
создаваемой системы. (Как должны быть настроены адаптеры РОА? Какие режимы
активации должны быть разрешены в данном случае? Существует ли алгоритм
выбора подходящего сервиса CORBA и средства его интеграции в многократно
используемую среду?)
Компонентная модель CORBA (ССМ) и Enterprise JavaBeans (EJB)
абстрагируются от многих системных проблем на уровнях контейнеров и подсистем. ССМ
гарантирует EJB место в будущем, позволяя различным языкам программирования
прозрачно взаимодействовать с Java, также как Java может взаимодействовать
с COBOL, С, C++ и другими языками, допускающими использование IDL.
Сейчас OMG работает над развитием Model Driven Architecture™ (MDA —
архитектура, управляемая моделями). Члены OMG отметили, что создание новых
платформ для программного обеспечения промежуточного уровня превратилось в
непрерывный процесс и не похоже, чтобы он замедлил или прекратил свое развитие
а предвидимом будущем. С целью обеспечения неизменности стандартов 0MG
разработал и принял MDA в качестве основы для будущих спецификаций, В
спецификации MDA приложение определяются в виде независимой от платформы модели
(PIM — Platform-Independent Model) на универсальном языке моделирования
Unified Modeling Language (UML). Инструментальные средства MDA генерируют
одну или несколько моделей (PSM — Platform-Specific Models), зависящих от
платформы, на основе PIM данного приложения в соответствии со
стандартизованными преобразованиями OMG. На следующем шаге инструментальные средства
MDA генерируют интерфейсы, код заглушек и шаблонов, конфигурационные
файлы, собирают и вводят в действие файлы и код приложения на основе PSM. MDA
поддерживает стандарты, которые OMG должен опубликовать в ее составе, для
многих платформ, включая EJB, XML/SOAP, C#/.NET и СОВВА. Взаимодействие
платформ обеспечивается мостами или преобразующим кодом, генерируемым ин-
струментальными средствами MDA на основе моделей. Группы, разрабатывающие
промышленные стандарты, главным образом рабочие группы по предметным
областям OMG, быстро перешли в своей работе на MDA, так как спецификации,
сформулированные на основе моделей РВД. включают только функции и
поведение, относящиеся к деловой сфере, и, следовательно, переживут частные
технологии и тенденции.
Расширение архитектуры OMG за пределы СОКВА (которая пользуется той же
поддержкой, что и до создания MDA) придает новую жизненную энергию OMG,
а также приверженности этой организации стандартам предметных областей, не-
г технологий.
8.9. Ресурсы в Internet и во Всемирной паутине
mnr.omg.org/egi-bin/doc'formal/99-07-59
Спецификация OMG преобразования Java-IDL подробно описывает различные
преобразования ы ограничения, присущие использованию этого преобразования на IDL. Этот
документ имеется в форматах Postscript и PDF.
www,omg.org/technology/documents/formal/
corba_services_available_«lectro.htm
На этой странице сайта OMG приведен список спецификаций сервисов CORBA. Все
спецификации можно загрузить в форматах postscript и PDF.
www.omg.org/cgi-Mn/doc7orbos/9a-01-lS
В этом документе объясняются отличия RMI и CORBA. В спецификации Objects-
by-Value (OBV) описываются ключевые слова, принципы функционирования к
использование OBV с Java.
www.w3.org/TR/NOTE-ice
Данный сайт предоставляет исчерпывающее описание и обсуждение формата описания
открытого программного обеспечения Open Software Description Format (OSD). Для
того чтобы разобраться, какие задачи решает OSD (и его расширение Information and
Content Exchange Protocol — протокол обмена информацией и контентом), необходимо
понимание XML и Web-технологий.
opeпогЬ.exolab.org/openorb.html
ExoLab Group выпустила (и обновляет) открытую реализацию CORBA 2.4.1 для
разработки распределенных систем. OpenORB поддерживает адаптеры РОА и BOA и
содержит различные инструментальные средства (такие как ГОL-компиляторы).
openorb.exolab.org/services.html
Этот альтернативный источник открытого программного обеспечения предлагает
коллекцию полезных сервисов CORBA для разработчиков, создающих
CORBA-приложении. Имеются сервисы именования, устойчивых состояний и транзакций.
www.omg.org/cgi-bin/doc?formal/О1-06-07
i документ с описанием преобразования
www.omg.org/cgi-bin/doc?formal/98-12-09
Object Management Group, CORBAServices: Common Object Services Specification. De
cember 1998 (Описание общих объектных сервисов).
www.omg.org/cgi-bin/doc7ptc/99-10-03
Эта страница Web-сайта OMG предлагает Component FTF Edited Drafts, первоначально
опубликованные в 1999 г.
www.omg.org/technology/documents/formal/
internationalixatioD_and_tlBe.btm
Документ, помеченный январем 2000 г., описывает локализацию, операции с датами
и временем.
CORBA. Часть 2 417
mnr, omg.org/technology/documents/formal/mobile »<j*nt_faclH.ty.btm
Данная страница содержит ссылку на спецификацию средств мобильных агентов,
Mobile Agent Facility Specification.
vnw.ea .wustl.edu/-achjnldt/PDP/CBSE .pdf
На этом сайте представлена замечательная коллективная работа *Обзор компонентной
модели CORBA*.
Java.sun . com/J2a»/1 , Э/docs/giiide/rmi-iiop/rmi Iiop__pg.html
Учиться, работал — подход, принятый в атом хорошо написанном учебнике,
объясняющем как использовать RMI-ПОР. Руководство программиста RMI-IIOP включает
множество ссылок на информацию, полезную разработчикам-новичкам по RMt,
CORBA и НОР.
Ресурсы, посвященные компонентной модели CORBA
corbaweb.lif1.fr/Op*nCCM/
OpenCCM Platform является открытой реализацией компонентной молали CORBA с
открытыми исходными текстами. Финансируемая Исследовательской лабораторией
французского университета в Лилле {Laboratoire de Recherche en Informatique de
I'Universite des Sciences et Technologies de Lille, LIFL), эта реализация компонентной
модели CORBA является полезным ресурсом для разработчиков CORBA, чтобы без
больших затрат опробовать технологию ССМ.
wtnr.cs.wustl.«du/~schmidt/PDE'/CBSE.pd£
Данный документ предстааляет собой обзор компонентной модели CORBA и
объяснение того, почему ее стоит использовать в дополнение к другим средствам CORBA. Этот
хорошо написанный обзор васается дополнительной информации, связанной с
контейнерами, компонентами, средой развертывания и выполнения, на которую разработчик
может рассчитывать, используя ССМ.
Ресурсы, посвященные Model Driven Architecture™
www.omg.org/mda./index, htm
Архитектура, управляемая моделями, обещает объединить многие существующие
технологии в единое целое для разработки больших модульных систем. В документе
обсуждаются пути развития MDA.
Терминология
abstract, ключевое слово
АС ГО — четыре стандартных требования
(Atomic, Consistent, Isolated, Durable)
к надежным транзакциям
activation — активация
Asynchronous Method Invocation (AMI) —
асинхронный вызов методов
Basic Object Adapter (BOA) — базовый
объектный адаптер
call-by-value — вызов по значению
catalog, ключевое слово
Component Implementation Framework
(CIF) — среда реализации компонентов
{компонентная архитектура)
Component Interface Definition Language
(CIDL) — язык описания интерфейсов
Component Home, объект
component-managed persistence — продол-
жительность существования, управляемая
ConsomerAdmin, объект
container programming model — модель про-
греммирования с использованием
контейнеров
container-managed persistence —
продолжительность существования, управляемая
контейнером
conversational container — диалоговый
контейнер
CORBA Component Model (ССМ) —
компонентная модель CORBA
CORBAaervicea — сервисы CORBA
durable container — долговременный
контейнер
Dynamic Invocation Interface (DII) —
интерфейс динамических вызовов
Dynamic Skeleton Interface (DSI) —
интерфейс динамических скелетов
Enterprise JavaBe&ns, стандартная компо-
вентнав модель языка Java
entity.
Event Service — еервис событий
EvenlChannel, объект
Event Channel Factory, объект
flat transactions — плоские транзакции
HomeFinder, объект {ССМ-вариант сервиса
Interface Repository (IR) — репозиторкй
интерфейсов
Java Naming and Directory Interface
(JNDI) — Java-интерфейс сервисов имено-
push model —модель проталкивания
recoverable object — восстанавливаемый
recoverable server — восстанавливаемый
сервер
Request for Proposal (RFP) — официальный
документ OMG
Request, объект
RMI-ПОР — RMI поверх протокола ПОР
Security Service — сервис безопасности
servant — сервант
- архитектура.
Model Driven Architecture™ -
управляемая моделями
Name Component, объект
Naming, объект
NamingContext, объект
nested transactions — вложенные транзвя-
Notification Service — сервис уведомлений
Object activation — активация объекта
Object Constraint Language (OCL) — язык
объектных ограничений
Object Transaction Service (OTS) — сервис
объектных транзакций
Object-by-Reference, спецификация
Object-by-Value, спецификация
Open Software Description Format (OSD) —
формат описания открытого ПО
OperationDef, объект
passivation — дезактивация
Persistent State Definition Language
(PSDL) — язык описания устойчивых co-
Persistent State Service (PSS) — сервис
устойчивых соетоянкй
Portable Object Adapter (POA) —
переносимый объектный ал an тер
PortableRemoteObject, объект
process, компонент
publishes, ключевое слово
pull model — модель опроса
shared transaction — разделяемая траязак-
stateless container — контейнер без сохране-
storagehome, ключевое слово
storagetype, ключевое слово
StroeturedEvent, объект
StructuredProxyPnshCoiUDmer, объект
S tractoredProxyPushSupplier, объект
Supplier Admin, объект
TIE (Object Adapter in delegation model) —
объектный адаптер в модели
делегирования
Trader Constraint Language (TCL) — язык
грейдерных ограничений
Transactional client — транзакпновный кли
- трав зек ционные
Transactional objecte -
объекты
TraneactioBal server — транзакционный
сервер
Unified Modeling Language (UML) —
универсальный язык моделирования
unit of work — единица работы
Unshared transaction — неразделяемая
транзакция
XML Metadate Interchange (XMP) — обмен
метаданными XML
Упражнения для самоконтроля
8.1. Заполните пропуски в каждом из следующих предложений:
a) Связь между именем и значением называется .
b) В контексте обработки транзакций ACID означает
c) Использование сервиса событий осуществляется с помощью .
d) В RMI-ПОР объект используется вместо java.rmi.UnicastRemoteObject.
e) DII позволяет клиенту осуществлять вызовы с определением типа.
f) предоставляют компонентам среду, в которой они могут
функционировать, и сервисы, которые они могут использовать.
Определите, является ли каждое из следующих утверждений истинным или ложным.
Если ложное, объясните почему.
a) RMI использует тот же протокол, что и CORBA.
b) Адаптеры ТШ являются адаптерами РОА. которые используют модель делегирования.
CORBA. Часть 2
419
c) Сервис событий позволяет одновременно использовать только одну модель
доставки: или проталкивания, или опроса, но ве обе одновременно.
d) CORBA-компоненты Session эквивалентны сеансовым компонентам EJB без
сохранения состояния.
e) CORBА-компоненты Session эквивалентны сеансовым компонентам EJB с
сохранением состояния.
f) Спецификация Enterprise JavaBeans определяет использование сервиса событий.
g) ССМ поддерживает ниогопоточные контейнеры.
Ответы на упражнения для самоконтроля
8.1. а) привязкой имен, Ь) Atomic. Consistent, Isolated, Durable, c) EventChannelFactory.
d) javax.rmi.PortableBeinoteObject. e) динамическим 1) Контейнеры.
8.2. а) Ложь. RMI использует Java Remote Method Protocol (JRMP), CORBA использует
Internet Inter-ORB Protocol (HOP). RMI-ПОР использует ПОР. b) Истина, с) Ложь.
Сервис событий позволяет использовать любое сочетание поделай проталкивания и опроса
в любой комбинации по отношению к поставщикам и потребителям, d) Истина, е)
Истина, f) Ложь. Специфинация EJB требует только наличия сервисов именования, бево-
пасности, устойчивых состояний и транзакций, g) Истина.
Упражнения
8.3. Напишите сервис Onot«Service, который реботает с моделями проталкивания и
опроса, определите операцию, позволяющую клиентам регистрироваться и получать
цитаты (Quotes) (в модели проталкивания), и еще Одну операцию для возврета Quote в
соответствии с определенным именем Symbol (в модели опроса). Напишите тестовый
клиент, который получает вытолкнутые сервером объекты Quote и запрашивает у сервера
те же объекты.
8.4. Напишите клиент с графическим пользовательским интерфейсом, который выводит
информацию Quote в JTable. Используйте TableModel в качестве псоредника доступа
к данным Quote. Используйте клиентский посредник в качестве оболочки для
удаленного сервиса QnoteService. Используйте клиентский посредник в качестве оболочки
для объекта Quote (если это нужно).
8.5. Измените QnoteService так, чтобы возвращать массив объектов Quote на основе
массива входящих имен Symbol.
8.6. Напишите LoggingServlce. Одна из моделей для сообщений — подключение к объекту,
который будет доставлять сообщения соответствующим образом, исходя из их
важности. В API следует учесть источник, приоритет, тему и содержание. Системной
архитектуре следует учитывать приоритет качества обслуживания. LoggingServios должен
быть сервисом только для записи (сообщения фиксируются, но не выдаются). Учтите
в API возможность доступа нескольких пользователей. Какой режим активизации
может быть назначен РОА, чтобы решить перечисленные задачи?
8.7. С точки зрения реализации LoggingService может управлять доставкой сообщений
несколькими способами. Это классический компромисс между простым API
(подключиться к сервису и передать ему сообщения напрямую) и болез сложным, но и с большими
возможностями (сделать сервис более похожим на пул соединений, используемый
многими клиентами). Измените имя LoggingServies на LoggingConneetion и сделайте так,
чтобы этот сервис возвращал объект LogStre&m. LogStream учитывает источник,
приоритет, тему и содержание сообщении (также как и в предыдущем примере), только
теперь выравнивание нагрузки становится более ваным.
Цитируемые работы
1. «The Common Object Request Broker: Architecture and Specification» October 2000
(«Общий брокер объектных запросов: архитектура и спецификация», октябрь 2000 г.)
<www.onig.org/cgi-bin/dec7fonnaI/01-0Z-33>.
2. «Interfaces,* Security Services Specification May 2000 («Интерфейсы», Спецификация
сервисов безопасности, май 2000 г.) <www.onig.org/cgi-bin/doc?formal/2001-03-08>.
420
Глава 8
3. .Interfaces,. <www.omg.org/egi-bm/docTformal/2001-03-08>. (.Интерфейсы))
4. «Distributed Transaction Processing: The XA Specification,» («Обработка распределенных
транзакций: Спецификация ХА») <www.opengroup.org/pob9/catalog/cl93.htm>.
5. «Transaction Services Specification,» May 2000 («Спецификация сервисов транзакций»,
май 2000 г.) <www.omg.org/cgi-bin/doc7formal/2001-05-02>.
6. L. DeMichie), .Enterprise Java Beans™ Specification,» 23 October 2000: 29
(«Спецификация Enterprise JavaBeane™», 23 октября 2000 г.: 29).
7. .CORBAComponents, Joint Revised Submiesion» 2 August 1999 («Компоненты CORBA»
2 августа 1999 г.) <www.omg.org/cgi-bin/doc7orbos/99-07-01>.
8. «CORBA Components, Joint Revised Submission» («Компоненты CORBA»)
<www.omg.org/cgi-bin /doe 7orbos/99-07 -01 >.
9. DeMichiel, 29.
10. «RMI-IIOP Programmer's Guide,» («Руководство программиста по RM1-II0P»)
<ja va .япп. com/j2se/1.3/doca/gui de/rmi -iiop/r mi_Ilop_pg. html#Rest rictioaa>.
11. «Java Language to 1DL Mapping* June 2001 («Преобревоаание Java-IDL». июнь 2001
г.) <www.oMg.org/cgi-bin/doc7formal/01-06-07>.
Библиография
Ресурсы Object Management Group
1. Balen, H., M. Elenko, J. Jones and G. Palumbo, Distributed Object Architeeturea with
CORBA. New York, NY: Cambridge University Press, 2000. (Архитектуры
распределенных объектов и CORBA)
2. Hoque, Reaz, CORBA 3. Foster City, CA: IDG Books, 1998.
3. Siegel, J, CORBA 3. Second Edition. New York, NY: Wiley Computer Publishing, 2000.
я
Пиринговые приложения
и JXTA
Цели
• Понять архитектуру
пиринговых приложений.
• Понять, как работают
популярные пиринговые
приложения.
• Научиться создавать
пиринговые сетевые
приложения для мгновенного
обмена сообщениями,
используя технологии RMI
и Jini.
• Создать приложение
мгновенного обмена
сообщениями, используя
Multicast Sockets и RMI.
■ Познакомиться С технологией
J XT A
Нет!
Дай вкусить все это мне,
пройти весь путь
подобно равным мне.
Героям старины былой,
И вынести всю тяжесть,
и оплатить позволь мне
жизни радостной долги
болью, холодом и мглой.
Роберт Браунинг
Если мы не посвятим себя служению
человечеству, чему еще мы можем служить?
Джон Адаме
Мы есть то, что видим—
Генри Давид Торо -
Хорошие советники не нуждаются
в клиентах,
Вильям Шекспир
422
Глава 9
9.1. Введение
Системы мгновенного обмена сообщениями и приложения обмена файлами, на
пример, AOL Instant Messenger или Gnutella, приобрели огромную популярность,
изменив технологию взаимодействия между пользователями сетей В пиринговых
приложениях (Р2Р или peer-to-peer) каждый узел выполняет как клиентские, так
и серверные функции. Такие приложения распределяют информацию и
ответственность за функционирование системы среди участвующих во взаимодействии
компьютеров, повышая, таким обрезом, живучесть системы при выходе из строя
отдельных узлов.
В этой главе мы рассмотрим построение пиринговых сетевых приложений.
Используя Jini (глава 3), RMI (глава 2) и широковещательные сокеты, мы в качестве
практических примеров представим два приложения. Сначала мы разработаем
приложение мгновенного обмена сообщениями на основе Jini и RMI, чтобы
продемонстрировать возможности Jini и показать, как осуществляется интеграция Jini
с другими технологиями. Затем мы реализуем аналогичное приложение
мгновенного обмена сообщениями, используя широковещательные сокеты и технологию
RMI. Наконец, мы представим JXTA — это новая технология Sun Microsystems™
с открытыми исходными кодами, в которой определены протоколы
взаимодействия пиринговых сетевых приложений.
Пиринговые приложения и JXTA
423
9.2. Клиент-серверные и пиринговые приложения
Большинство сетевых приложений основаны на принципе, что все компьютеры
должны быть разделены по выполняемый функциям. Одни компьютеры,
называемые серверами, обычно используются для хранения программ и данных. Другие,
называемые клиентами, получают доступ к данным, хранящимся на серверах.
Поисковая система Yahoo!™ (www.yalioo.com) является примером
клиент-серверного приложения. Клиент передает запросы центральным серверам, на которых
имеются предварительно построенные по результатам поиска в Internet индексы.
Сервер делает запрос к своим базам данных, на основе чего предоставляет клиенту
запрошенную информацию.
Пиринговые сетевые приложения (Р2Р) отличаются от клиент-серверных
приложений. Вместо того чтобы разделять компьютеры по функциям, эти
приложения работают на всех компьютерах и как клиенты, и как оерверы, Р2Р-приложе-
ния похожи на телефонную сеть, то есть любой пользователь может, как говорить
(т.е. посылать информацию), так и слушать (т.е. получать информацию)1. На
рис. 9.1 перечислены некоторые известные пиринговые приложения.
Распределенные
приложения
Gnutella
KaZaA
Службы
мгновенных
сообщения
Телефоннзя сеть
Описание
Р2Р-технология, которая не использует центральные серверы, отсутствует
аутентификация, а пиринговые узлы ведут поиск файлов с помощью
распределенного поискового механизма.
Система предназначена для обмена файлами и является гибридом
между Gnutella и централизованными приложениями. Центральный
сервер аутентифицирует пользователей. Некоторые узлы осуществляют
поисковые функции, индексируя файлы, находящиеся в узлах системы.
В поиске файлов участвует несколько узлов, которые затем возвращают
список узлов, в которых находятся искомые файлы, после чего
осуществляется непосредственное соединение для передачи файлов.
Системы пиринговых сетевых приложений, которые позволяют
пользователям передавать короткие текстовые сообщения и файлы друг
другу. Большинство служб мгноаенных сообщений используют
центральные серверы, которые аутентифицируют пользователей, а также
маршрутизируют сообщения между узлами.
Позволяет осуществлять голосовое взаимодействие между удаленными
друг от друга пользователями.
Рис. 9.1. Известные Р2Р-приложения
9.3. Централизованные и децентрализованные
сетевые приложения
Приложение, использующее центральный сервер, иллюстрирует
клиент-серверное взаимодействие. Основной недостаток такой централизовяиной системы —
ее зависимость от центрального сервера. Если центральный узел (т.е. сервер)
выходит из строя, все приложение перестает работать. Возможности сервера
ограничивают производительность приложения в целом. Например, Web-сайты выходят из
строя, когда злоумышленники перегружают Web-сервер чрезмерным количеством
1 Е. Harold, JAVA Network Programming. Sebastopoi: O'Relly & Associates, Inc., 1997:
26-27.
424
Глава 9
запросов. Однако у централизованных архитектур также имеются и
преимущества, включая простое управление такими системами (например, контроль доступа
пользователей к системе осуществляется в одном месте).
Настоящие Р2Р-приложения являются полностью децентрализованными и,
следовательно, у них отсутствуют недостатки, присущие приложениям, которые
зависят от центральных серверов. Если узлы в сети Р2Р-приложений выходят из
строя, хорошо разработанные Р2Р-приложения продолжают работать. Р2Р-прило-
жения иначе используют распределенные вычислительные мощности. Например,
Freenet позволяет пользователям совместно использовать документы,
предотвращая всякую цензуру. Пиринговые сетевые архитектуры позволяют производить
поиск в реальном времени, получая актуальные на данный момент результаты.
Централизованные поисковые системы с задержкой помещают вновь созданные
и измененные в Web данные в свои каталоги. Поиск данных с помощью Р2Р-при-
ложений корректно отражает состояние сети во время выполнения запроса1.
Пиринговые приложения имеют также и недостатки. Любой пользователь,
имеющий соответствующее программное обеспечение, может присоединиться
к пиринговой сети и оставаться при этом анонимным. Определить, кто находится
в сети в данный момент Времени, трудно. Отсутствие центрального сервера
препятствует защите авторских прав и интеллектуальной собственности. Поиск в
реальном времени в сети Р2Р-приложеиий может занимать много времени и
существенно увеличить сетевой трафик, потому что каждый запрос должен быть передай
всем узлам сети.
Настоящие клиент-серверные приложения являются полностью
централизованными, тогда как настоящие Р2Р-приложения являются полностью
децентрализованными. Многие приложения наследуют свойства этих подходов для
достижения специфических целей. Например, некоторые приложения для обмена
файлами не являются настоящими пиринговыми сетевыми приложениями, потому что
они используют центральный сервер для аутентификации пользователей и
индексирования совместно используемых файлов. Однако пиринговые узлы
соединяются друг с другом непосредственно для передачи файлов. В такой системе
централизация увеличивает производительность поиска, но делает сеть зависимой от
центрального сервера. Пересылка файлов непосредственно между узлами уменьшает
нагрузку на центральный сервер.
9.4. Поиск и обнаружение узлов сети
Обнаружение узлов — это действие, направленное на поиск узлов Р2Р-прило-
жения. Децентрализованный характер приложения замедляет обнаружение узлов
и поиск в них информации. Gnutella предлагает подход для преодоления этой
трудности. Gnutella представляет собой пиринговую сетевую технологию, которая
может осуществлять хранение и поиск распределенной по узлам информации.
Пользователи могут осуществлять поиск и загрузку файлов из любого узла сети.
Пользователи сначала подключаются к Gnutella с помощью указания сетевого
адреса известного узла Gnutella. Без знания, по крайней мере, одного узла
пользователь не сможет подключиться. Пользовательское программное обеспечение
Gnutella функционирует как сервер и использует протокол HTTP для поиска и
передачи файлов.
Для выполнения поиска узел рассылает поисковый запрос нескольким
близлежащим узлам. Затем эти узлы распространяют поиск на всю сеть пиринговых
узлов. Если какой-либо узел может удовлетворить критериям запроса на поиск, то
S. Waterhouea. «JXTA Search: Distributed Search for Distributed Networks.. May, 2001.
s earch. jxta .org/JXTAsearch .pdf.
Пиринговые приложения и JXTA 425
он передает ответ инициатору запроса. Затем инициатор соединяется
непосредственно С этим узлом и загружает необходимую информацию. Узел, который
предоставил искомую информацию, теряет свою анонимность только тогда, когда он
соединяется с инициатором запроса для передачи файла.
В Р2Р-приложеннях Freenet файлы распространяются через сеть пиринговых
узлов. Каждый узел Freenet передает запросы на поиск другому узлу. Если
выполнение вапроса на данном узле не было успешный, то узел, получивший запрос,
пересылает его следующему известному близлежащему пиринговому узлу. Если мы
рассмотрим пиринговый узел, производящий поиск, как вершину иерархической
структуры Gnutella, то обнаружим, что запросы на поиск осуществляют ее обход
в ширину, потому что узлы посылают запросы на поиск на несколько пиринговых
узлов сразу. Freenet работает в сущности также как и Gnutella, с тон лишь
разницей, что обход структуры осуществляется по принципу сначала в глубину.
Поиск, осуществляемый в Gnutella и Freenet, называются распределенным
поиском. Распределенный поиск делает сетевые приложения более устойчивыми, по-
-ому что устраняется главная причина отказов — центральный оврвер.
Информация, полученная посредством распределенного поиска, является актуальной,
потому что она отражает текущее состояние сети. Пиринговые узлы могут искать не
только информацию, но и другие узлы.
9.5. Практический пример. Deitel Instant Messenger
В следующих нескольких разделах мы рассмотрим Р2Р-приложение,
позволяющее пользователям обмениваться мгновенными сообщениями. Приложение
Deitel Instant Messenger использует технологию Jlni для регистрации
пользователей в сети пиринговых узлов. Поисковые сервисы Jlni хранят ссылки на
удаленные узлы сети. Пиринговые узлы используют технологию RMI для создания и
поддержания соединений друг с другом. Хотя мы иногда говорим об экземпляре
приложения в узле как о клиенте, тем ни менее каждый экземпляр приложения
является и клиентом, и сервером одновременно.
В основном окне приложения (рис. 9.2, слева) представлен перечень
пиринговых узлов в локальной сети, на которых запущен Deitel Instant Messenger. Для
отправки мгновенного сообщения пользователь выбирает имя в списке и нажимает
кнопку Connect. После этого появляется диалоговое окно (рис. 9.2, справа) с
выбранным именем в заголовке. Пользователь может ввести сообщение и затем
послать его, нажав кнопку Send.
Рис. 9.2. Окна приложения Deitel Instant Messenger
Deitel Instant Messenger использует технологию Jini корпорации Sun, которая
требует, по крайней мере, одного поискового сервиса. Однако с одним поисковым
сервисом приложение представляет собой гибрид пирингового и клиент-сер вер но-
426
Глава 9
го приложения. Поисковый сервис централизован для того, чтобы пиринговые
узлы могли находить друг друга. Узлы используют технологию НШ для
непосредственного взаимодействия друг с другом. Схема только с одним поисковым
сервисом похожа на схему, которую используют сегодня многие современные сервисы
обмена мгновенными сообщениями.
Для того чтобы Deitel Instant Messenger функционировал как Р2Р-приложение,
поисковый оернис должен функционировать на каждом уахе. Однако выполнять
поисковый сервис на каждом узле неэффективно, так как это порождает
существенный сетевой трафик. Таким образом, выбор между использованием одного
поискового сервиса и поисковым сервисом в каждом узле определяется
компромиссом между надежностью и требованиями к пропускной способности сети.
Бели в узле отсутствует поисковый сервис, то он зависим от поискового сервиса
в сети и это является результатом централизации. Клиентское приложение
состоит из посредника сервиса Jini и объекта RMI, что делает возможным пиринговое
взаимодействие. Приложение регистрируется всеми поисковыми сервисами,
использующими как однонаправленный, так и широковещательный поиск узлов.
Пользоветель может указать поисковый сервис путем выбора пункта Add Locator
(Добавить поисковый оервис) в меню File (Файл) и ввода унифицированного
указателя ресурса поискового сервиса.
После регистрации клиентского посредника поискового оервисе клиент Deitel
Instant Messenger запрашивает посредники других уелов в сети с помощью
поискового сервиса Jini. Эти посредники представляют все известные пиринговые узлы.
Для того чтобы послать сообщение, клиент должен хранить ссылку на другой
удаленный узел. Поэтому для-начала общения клиакт посылает ссылку сам себе через
посредник другого узла. Другой узел отвечает, посылая удаленную ссылку узлу,
инициирующему общение. Когда стороны имеют ссылки друг на друга, то они
могут посылать и принимать сообщения.
Основные шаги в данном примере следующие:
1. определить интерфейс сервиса, который содержит уделенную ссылку на
реализацию сервиса,
2. определить реализацию сервиса,
3. определить мэтоды начальной регистрации узлов в группе,
4. скомпилировать и запустить Р2Р-приложение.
Мы подробно обсудим каждый шаг в последующих разделах по мере
разработки приложения Deitel Instant Messenger.
9.6. Определение интерфейса сервера
Первый шаг в этом примере — определить интерфейс сервиса IMService
(рис. 9.3). Мэтод connect (строки 16-17) позволяет удаленным пользователям
передавать удаленную ссылку IMPeer узлу системы мгновенного обмена
сообщениями. Удаленные ссылки обеспечивают однонаправленную связь. Для того чтобы
установить двунаправленную связь, клиент должен иметь удаленные ссылки на
другие клиентские объекты IMPeer.
1 // IMService.java
2 // Интерфейс XXSarvice определяет методы,
3 // с помоцыо которше посредник
4 // взаимодействует с сервисом.
5 package соя.deitel.advjntpl.jini.Ш.service;
6
7 // базовые пакеты Java
Пиринговые приложения и JXTA 427
8 import java.rmi.*;
9
10 // пакет» Deitel
11 import com.deitel.advjhtpl.jini.IM.lMPeer,-
12
13 public interface iHService extends Remote (
14
15 // возвращаем ссылку RMI на удаленный объект IMPeer
16 public IHPeer connect( IHPeer sender )
17 throwa RemoteException;
ia 1
Рис. 9.3. Интерфейс IMService определяет, как посредник взаимодействует с сервисом
IMPeer (рис. 9.4) определяет интерфейс взаимодействия между пиринговыми
узлами.
3 // IMPeer.Java
2 // Интерфейс, который должны реалиэовнвать все Р2Р~приложения
3 package com.deitel.advjhtpl.jini.IH;
4
5 // базовые пакеты Java
6 import java.rmi.*,'
7 import java.util.*;
8
9 public interface IHPeer extends Remote
10 i
11 // отправка сообщения пиринговому узлу
12 public void sendHesaage< Message message )
13 throws RemoteException;
14
15 // информационные иетоды
16 public String getNameO throws RemoteException;
17 }
Рис. 9.4. Интерфейс IMPeer определяет взаимодействие между пиринговыми узлами
Методу connect (строка 16) а качестве параметра передается объект IMPeer, он
возвращает ссылку типа IMPeer.
В строке 9 IMPeer расширяет интерфейс java.rmi.Remote, потому что объекты
IMPeer являются удаленными объектами. Клиент Deitel Instant Messenger
посылает сообщение пиринговому узлу путем вызова его метода sendMessage (строки
12-13) и передает объект типа Message в качестве параметра. Класс Message
(рис. 9.5) представляет собой сообщение, которыми объекты IMPeer могут обмени
1 // Message.Java
2 // Message представляет объект, который может быть послан
3 // IHPeer содержит раквиаиты отправителя и содержимое сообщения.
4 package com.deitel.advjhtpl.jini.Ш;
5
6 // базовые пакеты Java
7 import java.io.Serialieable;
8
9 public class Message implements Serializable
428
Глава 9
10 (
11 private static final long SerialVeraionOID = 20010808L;
12 private String from,
13 private String content,-
14
15 // хоиеттруктор Невежде
16 public Message( String messageSenderHame,
17 String messageContent )
18 <
19 from = messageSenderName;
20 content = meвsag«Content;
21 1
22
23 // получаем представление строки
24 public String toString()
25 {
26 return from + ": " + content + "\n";
27 )
29
29 // получаем сообщение с именем отправителя
30 public String getSenderMama()
31 {
32 return from,-
33 )
34
35 // получаем содержимое сообщения
36 public String getContent()
37 (
38 return content;
39 )
40 1
Рис. 9.5. Класс Message определяет объект сообщения, которыми обмениваются
пиринговые узлы
В качестве домашнего задания доработайте этот класс так, чтобы осуществить
более сложные ниды взаимодействий. В строке 9 определен класс Message,
реализующий интерфейс Serializable, потому что объекты сообщений должны быть се-
риализованы для доставки ПО RMI. Конструктор Message (строки 16-21)
принимает в качестве параметра имя отправителя и содержание сообщения.
9.7. Определение реализации сервиса
Второй шаг в этом примере — определение реализации сервиса IMServicelmpl
(рис. 9.6), который, в свою очередь реализует интерфейс IMService. В строках
18-19 объявляется, что класс IMServicelmpl расширяет Unices tRemoteObject,
который отвечает ва экспорт IMServicelmpl в качестве удаленного объекта.
1 // IMServiceinpl.java
2 // IMServicelfflpl реализует интерфейс IHService
3 // на стороне сервера приложения IM
4 package com.deitel.advjhtpl.jini.IM.service;
5
6 // основные пакеты Java
7 ±mport java.io.*;
Пиринговые приложения и J XT A
8 import Java.nni.server.UnicestRemoteObjееt;
9 import java.rmi.RemoteException;
10 import java.util.StringToJcenizer;
11
12 // пакет» Deitel
13 import com.deitel.advjhtpl.jini.IH.IMPeer;
14 import com.deitel.advjhtpl.jini.IH.lMPeerlmpl;
15 import com.deitel.advjhtpl.jini.XH.Message;
16 import com.deitel.advjhtpl.jini.IH.client.IMPeerListener;
17
IS public claaa IHServicelmpl extends DnicastRemoteObject
19 implements ZHService, Serializable {
20
21 private static final long SerialVereionDID = 20010S08L;
22 private String userName = "Anonymous";
23
24 // у конструктора IMSarvice параметры отсутствуют
25 public IHServicelmpl{) throws RemoteExceptionl)
26 // конструктор ZHService принимает в качестве параметра
27 // имя пользователя
28 public IHServicelmpl[ String name J throws RemoteExceptii
29 {
30 userName - name;
// задаем имя пользователя
public void setDserNamef String i
// возвращает RHI-ccwixy на IMPeer на стороне получателя
public IMPeer connect! IMPeer sender }
throws RemoteException
<
// передача GOI и IMPeerImpl удаленному уелу
IMFearListener listener =
new IMPeerListener( usexHame );
// добавляем удаленный узел в пользовательский i
listener.addPeerf sender );
//посылаем ему свой IMPeerImpl
// завершение метода соединения
Рис. 9.6. Реализация сервиса IM Service Impl
Второй конструктор (строки 28-31J принимает в качестве строкового параметра
имя пользователя. Это имя отображается в окне PeerList. В строках 40-56 описан
метод connect.
430
Глава 9
Для того чтобы двум пиринговым узлам связаться между собой, нужно иметь
ссылки друг на друга. Следующие шаги кратко описывают процесс установления
связи:
1. Узел А посылает обращение сам себе через узел В путем вызова метода
connect объекта IMService.
2. Узел В сохраняет эту ссылку (строка 51) иа узел А для последующего
использования ее во время установления соединения.
3. Узел В возвращает ссылку на себя узлу А.
Deitel Instant Messenger создает объект класса IMPeerListener (рис. 9.7),
представляющий собой интерфейс пользователя, осуществляющего взаимодействие.
В верхнюю текстовую область выводится сообщение, передаваемое с помощью
удаленной ссылки на IMPeer. Нижняя текстовая область содержит текст,
отправляемый с помощью вызова удаленного метода.
1 // IMPeerListener.3*va
2 // IMPeerListener расширяет jFrame и обеспечивает интерфейс
3 // пользователя для диалог* с другими пиринговыми умами
4 package com.deitel.advjhtpl.jini.IM.client;
5
6 // основные шкет Java
7 import java.awt.*;
3 insert java.awt.event.*;
9 import java.rmi.RemoteException;
10
11 // пакет расширений Java
12 import javax.swing.*;
13 import javax.swing,text.*;
14 import javax.swing.border.*;
15
16 // пакеты Deitel
17 import com.deitel.advjhtpl.jini.IM. IMPeer;
18 import com.deitel.advjhtpl.jini.lM.Message;
19
20 public class IMPeerListener extends JFrame {
21
22 // Область JTextArea для отображения ж ввода сообщений
23 private JTextArea messageArea;
24 private JTextArea inputArea;
25
26 // Действия по отправка сообщений и т.д.
27 private Action sendAction;
28
29 // Имя польяоватаяя добавляется к исходящим сообщениям
30 private String userName =• ""
31
32 // IMPeer для отпрвлкн сообщений пиринговому уаху
33 private IMPeer remotePeer;
34
35 // конструктор
36 public XMPeerListener{ String name )
37 (
38 super{ "Conversation Window" );
39
40 // задаем имя пользователя
Пиринговые приложения и J XT A
userName = name;
// создаем JTaxtArea для отображения сообщений
nessageArea = new JTextArea{ 15, 15 );
// блокируем редактирование и перенос слов в конце строки
nessageArea.setEditable{ false );
messageAxea.setLineHrapf true );
messageArea.setHrapStyleWord{ true );
JPanel panel = new JPanel{);
panel.setLayout{ new BorderLayout{ 5, 5 ) );
panel.add( new JScrollPanef nessageArea ),
BorderLayout.CENTKM );
// создаем JTextArea для ввода новых сообщений
inputArea = new JTextArea{ 4, 12 );
inputArea.setLineWrap{ true );
inputArea.setWrapStyleWordf true ) ;
// преобразуем ввод с клавиатуры в inputArea в команды sendAction
Keymap keyMap - inputArea.getKeymapO ;
Keystroke anterKey = Keystroke.getKeyStroke{
KeyEvent.VK_ENTER, 0 );
keyMap.addActionForKeyStroke( enterKey, sendAction );
// размещаем inputArea и sendAction JButton в Box
Box box = Box.createVerticelBoxO ;
box.add( new JScrollPanef inputArea ) );
box.add( new JButton{ sendAction ) );
panel.add{ box, BorderLayout.SOOTH );
// размен»ние компонентов
Container container = gatContentPane{);
container.add{ panel. BorderLayout.ивигкк );
setSizef 200, 400 );
setvisible( true );
// действие по отправке сообщений
private class SendAction extends AbstractAction {
// настройка SendAction
public SendAction()
(
putValue( Action.HAKE, "Send" );
putValue( Action.SHORT_DESCRIPTION,
"Send Message" ) ,*
putValue( Action.LONG__DESCRIPTION,
"Send an Instant Message" );
98 // отправляем сообщение и очищаем inputArea
99 public void actionPerformed( ActionEvent event )
100 {
101 // отправляем сообщение серверу
102 try (
103 Message message = new Message( userName,
104 inputArea.getTextf) };
105
106 // используем RHI-ССЫлку Для отправки сообщения
107 remotaPeer.sendMessage( message );
108
109 // очиняем input&rea
110 inputArea.setText( "" );
111 displayMessage( message );
112 }
113
114 // перехват ошибки при о?правке сообщения
115 catch{ RenoteException remoteException ) {
116 JOptionFane.shOMMassageDialogt null,
117 "Unable to send message." );
118
119 remoteException.printStadcTracef);
120 )
121 ) // заверяете метода actionPerformed
123 ) // завершение тетода send&ction внутреннего класса
124
125 public void displayMessage( Message message )
12« {
127 // displayttaasage использует SwingOntilities.invokeLater
128 // для ивогопоточного доступа к message&rea
129 SwingUtilities.invoxeLater(
130 new MessageDisplayer(
131 message.getSenderNarae(), message.getContent(J ) );
132 )
133
134 // MessageDisplayer отображает новое сообщение, добавляя
135 // сообщение messageArea ■ JTextArea. Этот Runnable
136 // объект должен бить выполнен в программном потоке,
137 // управляемом событиями, так как это наменяет компонент Swing.
138 private class MessageDisplayer implements Runnable {
139
140 private String frooDser;
141 private String messageBody;
142
143 // конструктор MessageDisplayer
144 public MessageDisplayer( String from. String body )
145 (
146 fromUser = from;
147 messageBody ш body;
148 }
149
150 // отображает новое сообщение в messageArea
151 public void run{)
152 (
153 // добавление нового сообщения
Пиринговые приложения и JXTA
154 iMssageAxea.append( "\n" + fromUser +■ "> " +
155 mesaageBody );
156
157 // переводит курсор в конец messageArea, чтобы
158 // новое сообщекие отобразилось на экране
159 massageArea.setCaretPosition{
160 measageArea.getTextO .length() ) ;
161 )
162
163 } // конец внутреннего класс» HessageDiaplayer
164
165 // addPeer принимает IHPeer в качества параметра,
166 // чтобы связать IMPeer с sendAction для отправки сообщений
167 public void addPeer{ IHPeer peer ) throws RemoteException
168 {
169 remotePeer = peer;
170
171 // изменяем заголовок окна на имя узла
172 setTitlet remotePeer.getName() );
173 }
174 1
Рис. 9.7. Класс IMPeerListener реализует интерфейс пользователя и начинает
пиринговое взаимодействие
На рис. 9.6 в строке 48 объект IMPeerListener добавляется в объект IMPeer-
Impl с помощью метода addlastener. Метод sendMessage объекта IMPcerlmpI
посылает объект Message объекту IMPeerListener.
В строке 51 вызывается метод addPeer для добавления ссылки на удаленный
объект IMPeer в слушатель IMPeerListener. Это позволяет IMPeerListener послать
сообщение удаленному пиринговому узлу. Здесь необходимо отметить симметрию:
IMPeerListener является и клиентом, и сервером, потому что приложение само по
себе также является и клиентом, и сервером. Узлы должны хранить ссылки на
себя и уделенных партнеров, аналогично должен поступить и IMPeerListener.
IMPeerListener обеспечивает взаимодействие между двумя пиринговыми узлами.
IMPeerListener {рис. 9.7) является графическим пользовательским
интерфейсом для связи между пиринговыми узлами. Метод addPeer (строки 167-173)
сохраняет ссылку на удаленный узел IMPeer и изменяет заголовок диалогового окна
яа имя удаленного узла IMPeer. Когда пользователь нажимает кнопку JButton,
интерфейс пользователя вызывает метод sndMessage удаленного узла IMPeer
(строка 106), передавая содержимое input Are а в качестве параметра. Так клиент
посылает сообщения удаленным узлам. На стороне получателя удаленный IMPe-
erlmp] (рис. 9.8) вызывает метод display Method узла IMPeerListener для
отображения сообщения.
1 // iHPeerlmpl.java
2 // Реализация интерфейса IHPeer
3 package com.deitel.advjhtpl.]ini.IM;
5 // основные
6 import
7 import
В import
9 import
10 import
3ava
java
Java
java
java
пакет
io. * ;
net-*;
rmi.*;
Java
rmi-server.
util.*
I // пакеты Deitel
i import com.deitel.advjhtpl.jini.IM.Message;
i import com.deitel.advjhtpl.jini.IM.elieflt.IHPeerLietener;
J public class IMPeerlmpI extends UnicestReeoteOtoject
i implement» IMPeer (
private String леве;
private iMPeerLiatener output;
// хонструхяор бее параметров
public IMPeerlmpI() throve RemoteException
"anonymous",
// конструктор приникает в качестве параметра userMame
public IMPeerlmpI{ String myName ) throws RemoteException
public void eddListener( IMPeerListener listener )
output ■= listener;
// передаем сообщекие из этот узел
public void sendMessage{ Message message )
throws RemoteException
output.displayMessage( message );
// доступ ж имеки
public String getHaaef) throws RemoteException
Рис. 9.8. Класс IMPeerlmpI представляет собой реализацию интерфейса IMPeer
Класс IMPeerlmpI реализует интерфейс IMPeer. Экземпляр класса IMPeerlmpI
представляет узел во взаимодействии с другими узлами и позволяет пиринговым
узлам общаться друг с другом. IMPeerlmpI расширяет UnicastRemoteObject
{строки 17-18), чтобы экспортировать IMPeerlmpI, как удаленный объект. Метод
addListener (строки 35-38) добавляет объект типа IMPeer Listener, который будет
отображать действия IMPeerlmpI. Метод sendMessage (строки 41-45) вызывает
метод display Mess age интерфейса IMPeerListener для отображения сообщения.
Пиринговые приложения и JXTA
9.8. Регистрация сервиса
Третий шаг в этом примере представляет собой регистрацию сервиса пиринговой
группой. Класс IMServiceManager {рис. 9.9) принимает пользовательское имя типа
String и использует класс Jini JoinManager для регистрации сервиса всеми
известными поисковыми сервисами. Исходный текст похож на класс JoinManager
программы Serainarlnfo в главе 3. Отличие заключается в том, что конструктор
(строки 33-65) принимает String, который специфицирует Name Entry для сервиса.
1 // IMServiceManager.Java
2 // IMServiceManager использует JoinManager для обнаружения
3 // поисковых сервисов, регистрирует IMService ■ поисковых
4 // сервисах, управляет обновлением
5 peckaga com.deitel.advjhtpl.jini.IM;
б
7 // базовые пакеты Java
8 import java.rBn..RMXSecurityManager;
9 import java.rmi.RemoteException;
10 import java.io.IOException;
11
12 // базовые пакеты Jini
13 import net.jini.core.lookup.ServicelD;
14 import net.jini.core.entry.Entry;
15
16 // расширенные пакета Jini
lookup.entry.Name;
leasa.LeaseRenewalManagar;
lookup.JoinManager;
discovery.LookupDiscoveryManager;
lookup.ServicelDLiatener;
19 import net.j:
20 import net.j
21 import net.j
22
23 // пакеты Deital
24 import com.deitel.advjhtpl.jini.IM.service.*;
25
26 public claas IMServiceManager implements ServicelDListener {
27
28 JoinManager manager;
29 LookupDiscoveryManager lookupManager;
30 String serviceNane;
31
32 // конструктор принимает ник сервиса
33 public IMServiceManager( String screenNane )
34 (
35 System.setSeeurityManager( new RMlSecurityManager{) );
serviceNante =
// мспольауеи JoinManager для регистрации сервиса,
// управления сервисом и арендой
try (
// создаем LookupDiscoveryManager для обнаружения
// поискового сервиса
lookupManager =
new LookupDiscoveryManager{ new String{] { "" ),
436
Глава 9
48 null, null ) ;
49
50 // создаем и задаем имя для сервиса
51
52 Entry[] entries = new Entry[ 1 ];
53 entries[01= new Name{ serviceName );
54
55 // создаем JoinManager
56 manager = new JoinManager( createProxy(J,
57 entries, this, lookupManager,
58 new LeaseRenewalManagarf) );
59 )
60
61 // обреботка исключения при создания JoinManager
62 catch ( IOException exception ) {
63 exception.printstackTrace();
64 )
65 ) // запершение кошструктора SeminarInfoJoinService
66
67 // возврат XookupDiscoveryManager, соадатеого JoinManager
68 public LooxupDiscoveryManager getDiscoveryManagar()
69 <
70 return lookupManager;
71 )
72
73 // создание сервиса посредкика
74 private IMService createProxy()
75 (
76 // получение SeminarProxy для сервиса SeDiinarlnfo
77 try (
7в return{ new IMServicelmpl{ serviceName ) );
79 )
80
81 // обработка исключения при создании SeminarProxy
82 catch ( Remot«Exception exception ) {
83 exception.printStackTrace();
84 J
85
86 return null,'
87
SB } // завершение метода createProxy
89
90 // получение уведомления о присваивании идентификатора сервису
91 public void aerviceIDNotify( ServicelD servlcelD )
92 {
93 System.err.println( "Service ID: " + serviceID );
94 )
95
96 // информирование всех поисковых сервисов о завершении сервиса
97 public void logout()
98 (
99 manager.terminate();
100 }
101 )
Рис. 9.9. Класс IMServiceManager регистрирует IMServicelmpl в поисковых сервисах
Пиринговые приложения И JXTA
9.9. Поиск других узлов
awt.event.*;
net .MalformedURLException;
util.*;
«til.List;
io.IOException;
Класс PeerList (рис. 9.10), реализующий основное окно Deitel Instant
Messenger, строит список пиринговых узлов, которым пользователь может посылать
мгновенные сообщения. В строке 263 создается новый IMServiceManager,
передавая параметр userName (Имя пользователя) типа String конструктору IMService-
Manager для присвоения имени узлу.
1 // PeerList.Java
2 // Инициализация ServiceManager, обнаружение сервисов
3 // и отобреяение списка сервисов в окне
4 package com.deitel.advjhtpl.jini. IM;
5
€ // основные пакеты Java
7 import java.awt
8 import Java.
9 import Java.
10 import java
11 import Java
12 import Java
13 import Java
14
15 // пакеты расширений Java
16 import javax.swing.*;
17 import javax.swing.event.*;
IB
19 // основные пакеты Jini
20 import net.jini.core.lookup.Serviceltem;
21 import net.jini.core.lookup.ServieeTemplate;
22 import net.jini.lookup.*;
23 import net.jini.discovery.LookupDiscoveryManager;
24 import net.jini.lease.LeaseRenewalManager;
25 ftnport net.jini.lookup.entry.Name;
26 import net.jini.core.entry.Entry;
27 import net.jini.core.discovery.LookupLocator;
2B
29 // пакеты Deitel
30 import com.deitel.advjhtpl.^ini.IM.service.IMService;
31 import com.deitel.advjhtpl.jini.IM.client.iMPeerListener;
32
33 public class PeerList extends JFrame
34 implements ServiceDiscoveryListener (
35
36 private DefaultListModel peers;
37 private JList peerList,'
38 private List serviceltems;
39 private ServiceDiscoveryManager ServiceDiscoveryManager;
40 private LookupCache cache;
41 private IMServiceHanager myManager;
42 private LookupDiscoveryManager lookupDiscoveryManager ,-
43
44 // аноанммая инициализация userName
45 private String userName = "anonymous";
46
47 // метод вызывается, когда ServiceDiscoveryManager находит
48 // сервис мгновенных сообщений, сервис посредника добавляется
// к eerviceltems, а ник сервиса к LiatModel типе jList
public void aerviceAdded( ServiceDiscoveryEvent event )
(
// получаем добавленный serviceItern
ServiceItem item - event.getPostEventServiceltemf);
Entry attributes[) = itam.attributeSeta;
// просматриваем атрибуты для поиска имени
for( int i = 0; i < attributes.length; 1-м- )
if ( attribute»t i ] instanceof Name ) {
System.out.println( "Added: " + item );
serviceltems.add( item.service };
peers.addElement(
{ { Name )attributes[ i ] ).name );
break;
>
i метода aerviceAdded
// пустой метод игнорирует событие serviceChanged
public void serviceChangad( ServiceDiscoveryEvent event )
О
// удаляем сервисы из пользовательского интерфейсе PeerList и
// структуры данных, когда возбуждается событие serviceRemoved
public void aerviceRemoved{ ServiceDiscoveryEvent event )
(
// getPreEvent, потому что еяекент был удален
// getPoatEvent вернет null
ServiceItem item - event.getPreEventServiceltemf);
Entry attributes! ) = item.attributeSets;
// отладка
System.out.println( "Remove Event!" );
// удаление ив списка и DefaultListModel
int index = serviceltems.indexOf( item.service );
System.out.println( "Removing from List:" +
serviceltems.remove{ index ));
System.out.println( "Removing from DefList"
peers.alementAt( index ) ),-
peer». removeElenentAt ( index ) ,-
}
} // ватершеяие метода ServiceRemoved
100 // конструктор
101 public PeerList()
102 (
103 super! "Peer Liat" );
Пиринговые приложения и J XT A
105 System.setSecurityManagerf паи RMISecurityManager<) );
106
107 // получ<
108 userName = JOptionPane.showInputDialog(
109 PeerList.this, "Please enter your name: " );
110
111 // ишшпшм названия окна
112 setTitle( userHama + "'a Peer List Window );
113
114
115
116 '
117 Container container = getContentPan*0;
118 pears ■ new DefaultListHodelO ;
119
120 // инициализация компонентов
121 peer 1л.st = new JList( pears );
122 paerList.satVisiblaRowCountf 5 );
123 JButton connectButton = naw JButtonf "Connect" );
124
125 // аапрет ниохасваешюго «ыделиния
126 peerList.satSelectionModef
127 ListSelectionModel.SIHGLE_SBLECTION );
128
129 // задание обработчика событий для connectButton
130 connectButton.addActionListenerf
131 new ActionListenerO {
132
133 public void actionPerformed{ ActionBvent avant )
134 (
135 int itemlndex » peerIdst.getSelectadXndex{);
136
137 Object selectedService =
138 serviceltems.getf itemlndex );
139 IHService pearProxy *
140 { IHService )selectedService;
141
142 // поением даюше удаленному уалу,
ИЗ // получаем ВНТ-ссылжу
144 try (
145
146 // создание польаояателъеког-о интерфейса и peerlmpl
147 XMPeerListener gui =
148 new IMPeerListener{ userHaae );
149 IMPearlmpl me =
150 naw IMPearlmpl{ userHama );
151 me.addListenerf gui );
152 // саяодваиие пользовательского интерфейса
153 // с удаленным, объектом IMPaer
154 IKPaar myPeeг ■ pearProxy.connect( me };
155 gui■addPeer( myPeer );
156 )
157
15S
159
160 JOptionPane.showHeasageDialog;
161 { null, "Couldn't Connect" );
162 re.printStackTrace() ;
163 )
164 )
165 }
166 ); // завершение connectButton actionListener
167
168 // создание йена File
169 JMenu filaMenu = пен JHenu ( "File" );
170 filaMenu.setHnemonic( 'F' };
171
172 // подменю «О программе»
173 JMenuItem aboutItem = new JMenuItem( "About..." );
174 about Item. setHnemonic ( 'A' ) ;
175 aboutItem.addActionListener{
176 new ActionListener() {
177 public void actionPerformed( ActionEvent event }
178 (
179 JOptionPane.showMassageDialog( PeerLiat.this,
180 "Deitel Instant Messenger" ,
181 "About", JOptionPane.PLAINJfflSSAGE ) ;
182 )
183 )
184 ) ;
185
186 filaMenu.add{ aboutItem );
187
188 // элемент AddLocatoc
189 JMenuItem federateItem =
190 new JMenuItem( "Add Locators" );
191 federateltem.setMnemonic( 'L' );
192 federateltem.addActionListener{
193
194 new ActionListener{) {
195 public void actionPerformed( ActionEvent event )
196 (
197 // попучамме url поискового сервиса для добавления
198 String locator =
199 JOptionPane.ehowInputDialog(
200 PeerList.this,
201 "Please enter locator in this" +
202 "form: jini://boat:port/" );
203
204 try (
205 LookupLocator newLocator =
206 new LookupLocator( locator );
207
208 // создание элемента массива LookupLocator
209 LookupLocator[] locators = { newLocator };
210
211 // addLocators принимает массив
212 lookupDiscoveryManager.addLocators{ locators );
213 )
214
215 catch ( MalfomedORLException urlException) {
Пиринговые приложения и JXTA 441
216
217 JOptionРапе.ahowMessageDialog(
21S PeerList.this, "invalid url" );
219 )
220 )
221 }
222 ) ;
223 fileMenu.add( federateItern );
224
225 // создание JHenuBar и добавление а него пеня File
226 JHenuBar menuBar = new JMenuBarf);
227 menuBar.add { rileMenu );
228 setJMenuBarf menuBar );
229
230 // обработка события закрытия окна
231 addWindowListener(
232 new WindowAdapterО{
233 public void windowCloaing( windowEvent w )
234 {
235 System.out.println( "CLOSING WINDOW" );
236
237 // разрыв ездой с поисковыми сервисами
238 myM&nager.logout();
239 System.exit{ 0 );
240 }
241 }
242 ) ;
243
244 // размещезме компонентов пользовательского интерфейса
245 peerList.setFixedCellHidthf 100 );
246 JPanel input Panel = new JPanel () ,-
247 itiputPanel,add( conneetButton ) ;
248
249 container.add( new JScrollPanef peerLiat ) ,
250 BorderLayOut.NORTH );
251 container.add{ inputPanel, BorderLayout.SOUTH );
252
253 aetSize( 100, 170 );
254 setVisible( true );
255
256 // в списке узлов отображаются только другие IMServices
257 Clasi[] types = new Clasa[] { iMServiee.class };
258 ServiceTeotplate IMTemplate =
259 new ServiceTemplate( null, types, null );
260
261 // инвщиаякэация IMServiceManager, Services iscoveryHanager
262 try (
263 myttanager = new IMServiceHanager( userName );
264
265 // сохранение LookupDiscoveryManager,
266 // порожденного IMServiceHanager
267 lookupDiscoveryManager = myManager.getDiscoveryManager();
268
269 // ServiceDiBCOveryHanager использует lookupDiscoveryManager
270 aerviceDiseoveryManager =
271 new ServiceOiscoveryManager( lookupDiscoveryManager,
272
273
274
275
27S
277
278
279
280
281
282
283
284
285
286
287
288
28»
290
291
292 )
// смдин* LookapCacha
each* ™ aarviceDiscoveryManagar.createLookupCacha{
IMTaeplata, null, this );
)
// nepaxaav всех исключений и информирование
// аояьаоваталя об ошибках
catch { Exception managerException) (
J0ptio.nPane.ehowtta88agaDialog{ null,
"Error initializing iMSarvicaMangar" +
"or ServiceDiaoveryManagar" );
manag%xException.printStackTrace{);
)
public etatic void main{ String arge[] )
(
new PaarListO ;
Рис. 9.10. Класс PeeriJst представляет собой пользовательский интерфейс для поиска
пиринговых узлов
IMPeer (рис. 9.4) представляет собой интерфейс для взаимодействия между уз-
лами. Метод connect (строка 16) принимает в качестве параметра IMPeer и воавра.
щает ссылку на IMPeer. Удаленный интерфейс IMPeer описывает основные мето.
ды для взаимодействия с IMPeer.
LookapCache позволяет приложению реагировать на добавление, удаление
и изменение сервисов без постоянного опроса поисковых сервисов. PeerList
использует LooknpCache для определения подключения и отключения узлов от сети.
Для использования этой функциональной возможности приложение должно
передать объект ServiceDiscoveryListener методу createLookapCache. PecrList
реализует ServiceDiscoveryLiatener (строки 33-34), поэтому в строке 276 ссылка this пе-
редается методу createLookapCache. ServiceDiscoveryManager использует
удаленные события, зарегистрированные поисковыми сервисами, для выполнения
асинхронного уведомления сервисов ServiceDiseoveryEvent. Поетому мы должны
реализовать возможность загрузки файлов классов заглушек для обработчиков
RemoteEvent поисковыми сервисами. Раздел 9.10 объясняет, как это сделать.
Для реализации ServiceDiscoveryListener требуются методы eerviceAdded
(строки 50-66), serviceChanged (строки 69-70) и serviceRemoved (строки 74-98).
Метод Service Added вызывает метод getPostEventServiceltein класса Service-
Пиринговые приложения и JXTA 443
DiscoveryEvent для того, чтобы получить Serviceltem, который представляет
добавленный сервис. В строках 57-65 осуществляется обход атрибутов Serviceltem.
Если в строке 59 обнаруживается экземпляр объекта Name, то в строке 61 сервис
посредника добавляется в список, а в строках 62-63 осуществляется добавление
Name в Default List Model. Метод serviceChange является пустым, так как мы не
заботимся об изменении сервисами своих атрибутов. Метод serviceRemoved
вызывает метод get PreE vent Serviceltem класса ServiceDiscoveryEvent для того, чтобы
получить Serviceltem, который представляет удаленный сервис.
ActionListener (строки 130-167) объекта connectButtem получает индекс
выбранного элемента в JList и отыскивают посредник IMService, связанный с этим
индексом из serviceltem» списка (строки 138-141), В строках 147-148 создается
объект IMPeerListener, а в строках 149-150 создается объект IMPeerlmpl. В
строке 151 IMPeerListener добавляется в IMPeerlmpl. IMPeerlmpl перешлет все
сообщения, отправленные удаленным узлом IMPeerListener. С помощью метода
connect нз IM Service в строке 154 осуществляется передача удаленной ссылки
ШРсег на удаленный узел. В строке 155 осуществляется добааление возврещае-
мой удаленной осылки IMPeer в IMPeerListener, разрешая, таким обравом,
локальному пиринговому узлу посылать сообщения на удаленный пиринговый узел.
Вели эта последовательность событий вызывает RemoteException, то в строках
160-162 пользователь информируется об ошибке. Если же соединение
осуществляется успешно, то сервис возвращает IMPeer н взаимодействие происходит согласно
вышеизложенному описанию.
В строках 189-223 создается пункт меню Add Locator в меню File. При выборе
этого пункта меню появляется диалоговое окно-, которое подсказывает
пользователю адрес поискового сервиса Jini для добавления через однонаправленное
обнаружение. В строках 204-213 осуществляется добавление этого унифицированного
указателя ресурса в список LookupLocator, это завершает регистрацию клиента
в новом поисковом сервисе( а в окно PcerList выводится список всех пиринговых
узлов, зарегистрированных в новом поисковом сервисе.
9.10. Компиляция и запуск примера
Наконец, мы можем откомпилировать и запустить пиринговое приложение.
Это требует нескольких шагов. Первое, скомпилируйте классы, используя javac.
Затем скомпилируйте удаленные классы IMServicelmpl и IMPeerlmpl,
используя компилятор rmic для порождения классов-заглушек (см. главу 2). В
следующем шаге поместите классы-заглушки RMT, которые должны быть доступны для
других клиентов Deitel Instant Messenger и сервисов поиска (IMServiceImpI_Stnb
и IMPeerImpl_Stnb), в файл JAR (например, DlMdl.jar).
Для того чтобы сервисы поиска могли уведомлять наше приложение при
регистрации и заверщении работы пользователей, ServiceDiscoveryManager
нуждается в загрузке слушателя удаленного события. Перейдите в корневой каталог
Web-сервера и выполните следующие команды:
jar itvf C:\ jinil_l\lib\jini-ext.jar net\jini\lookup \ Service-
DiscoveryManager$LookupCacheImpl$LookupLiatener_Stub.class
jar *vf C:\ jinil_l\lib\jini-cor*.jar
net\jini\oore\event\ReeoteEventListener.class
При этом создается подкаталог net в корневом каталоге Web-сервера.
Чтобы использовать Deitel Instant Messenger, запустите демон активации RMI,
HTTP-сервер и сервисы поиска (см. главу 3). Для запуска Deitel Instant Messenger
перейдите в каталог, который содержит файлы приложения, и выполните
следующую команду:
444
Глава 9
java -Djava.aecurity.policy=policy.all
-DJava. nni.server.codebase=http://hoet г port/DIM_DL.jar
com.deitel.advjhtpl.j ini.IM.PeerLiet
Подставьте соответствующие значения для hoet, port и имени JAR-файла
(например, DIM_dl.jar). Опция codebase задает местоположение JAR-файла,
содержащего файлы заглушек RM1, которые удаленные узлы и сервисы поиска должны
загружать.
9.11. Доработка Deitel Instant Messenger
Реализация Deitel Instant Messenger не затрагивает вопросов безопасности
и масштабируемости. ServiceDiscovery Manager загружает посредник для каждого
пользователя в списке каждого сервиса поиска. По мере того, как все большее
количество пользователей и сервисов поиска подключается к сети, нагрузка на сеть
чрезмерно возрастает. Отсутствие средств обеспечения безопасности и механизмов
аутентификации обусловливает анонимность клиентов, потому что нет надежных
способов подтверждения, что пользователи являются именно теми, за кого они
себя выдают.
Есть различные способы решения этих проблем. Количество сервисов, которые
загружает ServiceDiscoveryManager, может быть ограничено фильтрами. Сервисы
поиска могут огрзличивать число управляемых ими сервисов. Приложение также
могло бы использовать распределенный поиск в локальных узлах (см. раздел 9.4).
Это включает в себя пересылку запроса поисковому сервису, который пересылает
запрос дальше, если не способен найти пользователя. Использование цифровых
подписей, открытых и секретных ключей может помочь в реализации средств
безопасности и аутентификации. Однако это не решает проблемы дублирования
имен пользователей в пиринговой сети. Эти решения не являются полными.
9.12. Реализация Deitel Instant Messenger
на основе Multicast Sockets
Deitel Instant Messenger использует JoinManager и ServiceDiscoveryManager
технологии Jini для объявления о существовании пиринговых узлов и их
обнаружения. Использование технологии Jini позволяет реализовать эти функции
добавлением всего нескольких строк кода. Чтобы придерживаться пиринговой
архитектуры, каждый узел должен поддерживать работу поискового сервиса. Сервисы
поиска требуют значительных ресурсов памяти и процессорного времени. В связи
с этим мы представляем улучшенную реализацию Deitel Instant Messenger,
которая использует многоадресные сокеты и простые протоколы для объявления и
обнаружения пиринговых узлов сети.
9.12.1. Регистрация узла
Технология Jini при резлизацин Deitel Instant Messenger обеспечивает
механизм, который позволяет узлам объявить о себе в сети и найти другие узлы. Бели
узел теряет соединение без явного разъединения (например, без вызова метода
terminate класса JoinManager), сервисы поиска Jini удаляют узел после
истечения срока аренды. Когда срок аренды истекает, сервис поиска Jini удаляет узйл.
В пользовательском интерфейсе Deitel Instant Messenger узал удаляется в окне
списка узлов. Так как приложение уже не связано с Jini, мы должны сделать это
сами.
Пиринговые приложения и JXTA 445
Первый механизм, который мы реализуем, — это класс
MuiticastSendingThread (рис. 9.11). MuiticastSendingThread оповещает о присутствии узла.
MuiticastSendingThread периодически осуществляет многоадресную рассылку, чтобы
уведомить другие узлы, что данный узел все еще доступен. Каждый узел на
получающей стороне линии обновляет аренду для многоадресной рассылки. В случае
если такой узел останавливает объявление о своем присутствии (например,
пользователь завершает приложение), срок действия аренды узла истекает и узел
прекращает свое существование в сети.
1 // MuiticastSendingThread.Java
2 // Периодически отправляет многоадресный пакет,
3 // содержащий удаленную ссылку, объекту IMServicelmpl.
4 package com deitel.advjhtpl-р2р;
5
6 // Базовые пакета Java
7 Import java.net.MulticastSoeket;
8 Import java.net.*;
9 Import java.nni.*;
10 Import java.nni.registry.*;
11 import java.io.*;
12
13 // Базовые пакеты Deitel
14 import com.deitel.advjhtpl.jini.IM.service.IMServicelmpl;
15 import com.deitel.advjhtpl.jini.IM.service.IMService;
16
17 public class MulticaetSendingThread extends Thread
18 implements IMConstents (
19
20 // InetAddress группы для сообщений
21 private InetAddress nmlticastNetAddress;
22
23 // MulticastSocket для многоадресных сообщений
24 private HulticastSocket multicastSocket;
25
26 // Повторно используемый пакет дейтаграммы
27 private DatagramFacket multicastPacket;
28
29 // Заглушка локального узла
30 private IHService peerStub;
31
32 // Флаг для прерывания рассылки MuiticastSendingThread
33 private boolean keepSending = true;
34
35 private String userName;
36
37 // Конструктор класса MuiticastSendingThread
38 public MulticastSendingThreadf String myName )
39 (
40 // Вызывается конструктор суперкласса, «тобы присвоить кия Thread
41 super{ "MuiticastSendingThread" );
42
43 ueerHame = myName;
44
45 // Соэдазм реестр на порту 1099 по умолчания
46 try {
47 Registry registry =
LocateRegistry.createRegiatry( 1099 );
peer Stub = natr IMServiceInx>l( паегМавш ) ;
registry.rabindf BIHDXNG_HAME, peerStub );
)
catch { RemotaExcaption renoteEacception ) (
remoteSxception.printStackTraceO;
// Создается многоадресный смет для рас силки сообщений
multicaatSocket =
new MulticaatSocket { HOLTIC&ST_SENDJNG_PORT );
// Задание времени жизни для многоадресного сонета
multicastSocket.setTiiaeToLiveC MULTICASTJTTL );
// Испояьвояаиие ioetAddresa, зарезервированного для
// многоадресной группы
ntulticaatNetAddresa = XnetAddress.getByName(
MDLTICAST_ADDRESS );
// Создание пакета приветствия
String greeting = пей String{ HELLO_HEADER + userName );
nulticastPacket » пей Da tagramPacket(
greeting.getByteaf), greeting.getBytea[).length,
multicastMetAddreea, MDLTICAST_LISTENING_PORT );
// MOLTICAST_ADDRESS является адресом неизвестного хоста
catch ( Java.net.UnknownHoatException unknownHostSxception
(
System.err.printlnf "MDLTICAST_ADDRESS is unknown" );
unknownHoatExcaption.printstackTraos();
)
// Любые другие исключения
catch ( Exception exception )
(
exception.printStackTrace();
)
// Досталха узлам приветственных сообщений узлам
public void run()
while { keepSending ) {
II Доставка приветствия
try (
99 // Отправлв!
100 multicaatSocket.send{ multicaafcPackefc );
101
102 Thread.al«ep{ MULTICAST_IHTErval );
Пиринговые приложения и JXTA
103 }
104
105 // Обработка исключений при достажке сообщений
106 catch { IOException ioException ) (
107 ioException.printStackTracef);
108 continue,-
109 )
110 catch { InterruptedException interruptedException ) {
111 interruptedException.printStackT»ce{) ;
112 )
113
114 ) // Окончание цикла while
115
116 multicastSocket.close();
117
US ) // Окончание метода run
119
120 // Отправление прощального сообщения
121 public void logout{)
122 {
123 String goodbye = new String( GOODBYE_HEADER + userName );
124 System.out.println( goodbye ) ;
125 nmlticaetPacket = new DatagranPacket(
126 goodbye.getBytes(), goodbye.getByteeO.length,
127 multicaetNetftddress, MULTICaST_LISTENING_PORT );
128
129 try (
130 ntulticaetSocxet.sendf multicastPacket );
131
132 Naming.unbind( EINDING_NAME );
133 )
134
135 // Ошибка многоадресной рассылки
136 catch { IOException ioException ) {
137 System.err.println("Couldn't Say Goodbye");
138 ioException.printstackTrace 0;
139 )
140
141 // Вониожяые исключения при отключении связи
142 catch { Exception unbindingException ) {
143 unbindingException.printStackTraceO;
144 )
145
146 keepsending « false;
147
148 )
149 )
Рис. 9.11. MuttkastSendingThread рассылает пакеты дейтаграмм
MulticastSendingThread расширяет класс Thread. В блоке try (строки 47-48)
вызывается метод createRegistry класса LocateRegistry, в который передается
номер порта 1099 для RMI-реестра, т.е. порта, который использует приложение
rmiregistry. В строке 49 создается новый объект IMServicelmpl. в строке 50 объ-
ект IMServicelmpl привязывается к реестру RMI, используя BINDING_NAME —
одну из многих констант, определенных в интерфейсе IMConstantsfpHC. 9.I2), ко-
448
Глава 9
торый реализует класс MnlticastSendLngThread. На рис. 9.12. приводится
интерфейс IM Const ants, в котором определены все константы, используемые в Deitel
Instant Messenger.
1 // IMConstanta.Java
2 // Содержит константы, используемые приложением Instant Messenger
3 package com.deitel.advjhtpl.p2p;
4
5 public interface IHConstants {
6
7 public static final String MULTICAST_ADDRESS = "228.5.6.10";
8
9 public static final int MCLTICASTJTTL = 30;
10
11 // Определение порта локальной машины для групповой рассыпки
12 public static final int MOLTICAST_SENDIHG_PORT = 6800;
13 // Определение порта локальной машины для приема
14 // ширеконещдтельной рассылки
15 public static final int HCLTICAST_RECEIVIMG_PORT = 6789;
16
17 // Определение порта группового адреса для отпраалвжмк пакетов
18 public static final int HCLTICAST_LISTENING_PORT = 6789;
19
20 public static final String HELLO_HEADER = "HELLOIM: ";
21
22 public static final String GOODBYE_HEADER = "GOODBYE: ";
23
24 // Задание времени в миллисекундах для интервала между рассыпкаыи
25 public static final Int MULTICAST_INTERVAL = 10000;
26
27 // Количество повторений интервалов, прежде чам истечет срок аренды
28 public static final int PEER_TTL = 5;
29
30 public static final int HESSAGE_SIZE = 256;
31
32 public static String BINDING_NAME = "IMSEKVTCE";
33
34 )
Рис. 9.12. Интерфейс IMConstants определяет константы в приложении Deitel Instant
Messenger
В строках 59-60 (рис. 9.11) создается многоадресный сокет Multicast Socket
для порта, который определяет константа MULTICAST_SENDING_PORT. В
строке 63 задается время жизни (TTL — Time To Live) для пакетов DatagramPacket,
отправленных через Multicast Socket. В строках 66-67 создается InetAddress с
помощью IP-адреса, определенного константой MULTICAST_ADDRESS.
В строках 70-74 создается пакет DatagramPacket, который содержит строку
с именем узла. HELLO_HEADER информирует псе узлы, ожидающие групповой
рассылки, что этот узел может получать сообщения. MULTTCAST_LISTE-
NING_PORT определяет порт для группового IP-адреса, на котором все узлы
осуществляют прослушивание. В блоке catch (строки 78-82) обрабатывается
исключение UnknownHostException, если MTJLTICAST_ADDRESS является
некорректным групповым адресом.
Пиринговые приложения и JXTA 449
В строке 100 рассылается объект MnlticastPacket, который был создай
конструктором. В строке 102 уточняется, что программный поток должен ожидать
между рассылками столько миллисекунд, сколько определено в MULTICAST_INTER-
VAL. В строке 116 прекращается работа Multicast Socket, когда пользователь
завершает приложение.
В строках 123-127 формируется пакет DatagramPacket, который содержит
строку с сообщением GOODBYE_HEADER и именем пользователя узла. GOOD-
BYE_HEADER указывает, что узел покидает сеть. В строках 130-133 Datagram-
Packet отправляется, а из RMI-реестра освобождается IMServicelmpI, связанный
с BINDING_NAME. В строке 146 задается значение false для keep Sending для
прерывания потока сообщений.
9.12.2. Обнаружение других узлов
В реализации Deitel Instant Messenger, выполненной с помощью Jini, сервис
обнаружения заносит в список новые узлы и удаляет узлы, покинувшие сеть. Класс
ServiceDiscovcryManager обновляет приложение, когда узлы были либо
добавлены, либо удалены. Для этого мы создаем класс MnlticastReceivingThread
(рис. 9.13), назначение которого ожидать поступления пакетов DatagramPacket,
содержащих уведомления о подсоединении узлов к сети или их удалении из сети.
1 // MulticastReceivingThread.Java
2 II Получаем и обрабатываем рассылки от многоадресных груша
3 package com,deitel.advjhtpl.p2p;
4
5 // Базовые пакеты Java
6 import Java.net.MulticastSocket;
7 import java.net.*;
8 import 'java.io.*;
9 import java.util.*;
10
11 // Пакеты Deitel
12 import com.deitel.advjhtpl.p2p.P*erDiscoveryListener;
13
14 public class MulticastReceivingThread extends Thread
15 implements IHConstants {
16
17 // HashMap, содержащий имена узлов и тремя,
18 // используемые при реализации аренды
19 private HashMap peerTTLMap;
20
21 // Ссыпка LeasingThread
22 private LeasingThread leasingThread;
23
24 // Объект, реагирующий на добавление или удаление узла
25 private PeerDiecoveryLietener peerDiscoveryListener;
26
27 // MulticastSocket для получении групповых сообщений
28 private MulticastSocket multicaetSocket;
29
30 // InetAddrese группы
31 private InetAddrese multicastNetAddress;
32
33 // Флаг для прерывания MulticastReceivingThread
34 private Ьоо1еал keepListening = true;
// Конструктор HulticastReceivingThread
public HulticaatReceivingThre*d( String userHame,
FeerDiscoveryLiatener pearEvantHandler )
t
// Вызов конструктора суперкласса, чтобы присвоит» i
super( "HulticaetReceivingThread" );
II задание слушателя peerDiacoveryListener
peerDisсоveryListener = peerEventHandler;
// соединение MulticastSocket с групповым адресом и портом
try (
multicast Socket *=
new MulticaatSockett MJLTICAST_RECEIVING_PORT J;
multiceatHetAddresa =
InetAddress.getByNamet MOLTICAST_ADDRESS );
// подключение многоадресной группы для получении сообщений
multicHstSocket.joinGroup( multicaatNetXddress );
// Установление предельного времени ожидания
multicastSocket.setSoTimeout( 5000 );
// Обработка исключения при соединении
catch( IO&xception ioException ) (
ioException.printStackTrace();
реегТПИар = new BashMapO ;
// Сведение потока Leasing, который уменьшает TTL уалов
leaaingThread = new LeasingThraad();
leaaingThread-aetDaemon( true );
leaaingThread.start();
) // окончание конструктора HulticastReceivingThread
// ожидание сообщения от многоадресной группы
public void run()
t
while ( keepLietening ) {
// создание буфера для приходямего сообщении
byte[] buffer = new byte[ MESSASIjSIZE ];
// создание DatagramPacket для приходящего сообщения
DatagranPacket packet « new DatagranPacket( buffer,
MESSAGE_SIZE );
// получение нового DatagranPacket (блокнрукщий аыаов)
try (
multicastSocket.receive( packet );
Пиринговые приложения и JXTA
92 // обработ
93 catch ( IntarruptedlOException interruptedlOException ) (
94
95 // следупщая итерация
96 continue;
97 >
98
99 // обработка исключая»» при счмтмааяюя наката
100 catch ( lOException ioException ) (
101 ioException.printst»ekTrace() ,-
102 break;
103 >
104
105 // попечение данных сообщения ш строну
106 String message « new String( packet.getData(),
107 packet.getOffset(), packet.gatLangth() );
108
109 // проверка
110 if ( message ! = null ) (
111
112 // удаление оробелой а
113 message » message.tria();
lid
115 System.out.println( message J•
116
117 // определение типа сообщения: goodbye кни hello
118 if ( message.startsNltht HELLO_HEADER ) ) (
119 procee«Hello(
120 message.substring( HELLOJSEADER.length(J ),
121 packet.getAddresst) .get&ostAddrass{)
122 J ;
123 }
124
125 else if ( message.atartaWitht GO0D8YE_HEADER ) )
126 processGoodbye( message.subatring(
127 GOODBYE_HEADER.length{) ) );
128
129 ) // охончание if
130
131 ) // окончание while
132
133 // выход на многоадресной группы и закрытие HulticastSockat
134 try (
135 multicestSocket.leeveGroBpl multicastHetAddress );
136 multicutSocket. close () ;
137 )
138
139 // обработка исключения при выходе ма rpymm
140 catch ( lOException ioException ) (
141 ioException.printstackTrace();
142 )
143
144 ) // окончание метода run
145
146 // обработка сообщения hello от уала
147 public void proceasHello( String peerName,
148 String registryAddress }
149 (
150 regietryAddreee += ( "/" + BINDIHG_NAHE );
151 synchronized( peerTTLMap )
152 (
153
154 // если это новый узел, возбуждаем событие peerAdded
155 if ( !peerTTLHap.containsKeyl peerName ) ) (
156 peerDiscoveryLietener.peerAdded( peerName,
157 regietryAddress);
158 )
159 // добавление узла * хэш или, если он ухе есть,
160 // обновление его TTL
161 peerTTLMap.putt peexHame, new Integer( PEER_TTL ) ) ;
// обработка сообщения goodbye от узла
public void processGoodbye( String peexName )
(
synchronized( peerTTLHap )
t
System.out.pxintln{ "Removing peer" + peerName );
if ( peerTTLMap.containsKeyt peerName ) ) {
peerDiacoveryListener.peerRemoved( peerName );
peerTTLHap.remove( peerName );
1
179 // периодическое уменьшение TTL узлов в списке
180 private class LeasingThread extends Thread
181 {
182 public void run I)
183 {
184 while ( keepListening )
185 t
186 // шаахтмакое состояние
187 try [
188 Thread.sleep( MULTICAST_INTBRVAL J;
189 )
190 // IntarruptedException может прервать
191 // неактивное состояние
192 catch ( interruptedException intarruptedException )
193 intarruptedExoeption.printStackTraceO;
194 }
195
196 // блокировка so время уменьшения TTL
197 synchronized! peerTTLHap ) (
198
199
200 Iterator peerlterator =
201 peerTTLHap.entrySetO.iterator{);
Пиринговые приложения и JXTA
203 while ( peer-Iterator.hasNextO ) {
204 // задание ноаого TTL узла
205 Map.Entry tempMapEntry =
206 ( Map.Entry ) peerIterator.next();
207
208 Integer tempIntegerTTL =
209 { Integer ) tempMapEntry.getValuet);
210 int tempIntTTL = tempIntegerTTL.intValue();
211
212 // уменьшение TTL
213 tempIntTTL—;
214
215 // если время аренды истекло, узел удаляется
216 if { terapIntTTI. < 0 ) t
217 peerDiscoveryListener.peerRemoved(
218 { string ) tempMapEntry.getKey() );
219 peerIterator.remove{);
220 )
221
222 // в противном случае задается новый TTL
223 else
224 tempMapEntry.setValue(
225 new Integer( tempIntTTL ) );
226
227 } // окончание цикла while no узлам
228
229 ) // окончание блоха синхронизации
230
231 } // окончание цикла while в методе run
232
233 } II окончание метода run
234
235 } II окончание класса LeasingThread
236
237 // прекращение прослушивания
238 public void logout()
239 {
240 // завершение потока
241 keepListening = false;
242
243 }
Рис. 9.13. Класс Multi cast ReceivingTh read использует программные потоки для
добавления и удаления узлов
В строках 48-55 создается MnlticastSocket на порту, определенном в константе
MULTICAST_RECEIVING_PORT, и добавляется в многоадресную группу
рассылки. В строке 58 указывается, что MuttieastSocket должен прекращать ожидание
получения пакета, если это занимает более 5 секунд. В строках 69-71
LeasingThread запускается как поток демона. Мы рассмотрим LeasingThread более
подробно далее в этом разделе.
В строке 89 осуществляется получение DatagramPacket от MnlticastSocket,
используя метод receive. Если время ожидания при выполнении метода receive
истекло, в блоке catch (строки 93-97) перехватывается исключительная ситуация
454
Глава 9
Interrupted! OExcept ion. В строках 106-107 считывается сообщение String,
хранящееся в полученном пакете Datagram Packet.
В строках 118-128 вызывается метод proceesHello (см. обработку сообщения
hello в строках 147-164), если сообщение начинается с константы Н£1ХО_НЕА-
DER. В строках 125-127 вызывается метод processGoodbye (см. обработку
сообщения Goodbye в строках 167-177), если сообщение начинается с GOODBYE_HEA-
DER. Когда цикл while заканчивается, в строках 184-187 отменяются
подключение к многоадресной группе, сокет закрывается.
Метод processHello обрабатывает сообщения, которые содержат HELLO—HEA-
DER. В строке 150 осуществляется добавление BINDING_NAME к строке,
содержащей IP-адрес узла, который отправил приветственное сообщение. Это
формирует унифицированный указатель ресурса RMI, с помощью которого узел может
связаться с вновь подключенными узлами. В строке 151 объект HashMap
синхронизируется с peerTTLMap для предупреждения доступа и воздействия на
peerTTLMap со стороны других потоков. Этот HashMap хранит имена узлов, как
ключи, и сроки аренды узла, как значения. В строке 155 проверяется, находится
ли в peerTTLMap имя узла, определенного в приветственном сообщении. Бели
такого узла там нет, то вызывается метод peerAdded объекта peerDiscoveryListener.
Каждый MulticastReceivingThread содержит ссылку на объект, который
реализует интерфейс PeerDiscoveryListener (рис. 9.14).
1 // PeerDiecoveryListener.Java
2 // Интерфейс просяуяиеянмя событий peerAdded или peerRemoved
3 package com.deitel.advjhtpl.p2p;
4
5 public interface PeerDiscoveryListener (
6
7 // Добавление узла с данным именем и IP-адресом
8 public void peerAdded( String name. String peerStubAddress );
9
10 // удаление узле с данным именем
11 public void peerRemovad( String name );
12
13 )
Рис. 9.14. Интерфейс PeerDiscoveryListener для прослушивания, когда узлы добавляются
или удаляются из групп
Методы peerAdded (строка 8) и peerRemoved (строка 11) информируют
реализацию PeerDiscoveryListener, что узел только что подключился или только что
был удален из многоадресной группы, соответственно. В строке 161 в классе
MulticastReceivingThread в peerTTLMap помещаются входные данные, которые
содержат имя узла и TTL (время жизни, определенное в PEER_TTL). Если узел
уже есть в peerTTLMap, то в строке 161 предшествовавшие данные замещаются на
новые, что приводит к обновлению времени аренды узла и его TTL. В строке 174
в классе MulticastReceivingThread удаляется узел из peerTTLMap. Так как
каждый узел непрерывно рассылает приветственные пакеты, пакеты могут быть
продублированы. Поэтому в цикле if (строка 172) вначале проверяется, есть ли дан-
вый узел в peerTTLMap, прежде чем попытаться удалить его. В строке 173
вызывается метод peerRemoved зарегистрированного peerDiscoveryListener, чтобы
информировать его, что узел уже покинул многоадресную группу.
В строках 180-235 определяется внутренний класс LeasmgThread.
Единственный поток периодически уменьшает TTL каждого узла в peerTTLMap. В строке
197 синхронизируется peerTTLMap, чтобы предотвратить конфликты переадреса-
Пиринговые приложения и JXTA ^ 455
дни между потоками сообщений, пытающихся обратиться к peerTTLMap
одновременно. В строках 200-201 осуществляется получение объекта Iterator для реег-
TTLMap. В строках 203-227 уменьшается TTL каждой записи в peerTTLMap.
В строках 208-210 осуществляется получение переменной типа bit, которая
содержит TTL узла, в строке 213 уменьшается значение TTL. В блоке if (строка 216)
проверяется, меньше ли нуля измененное значение TTL, это означает, что время
аренды узла истекло. Потом в строках 217-218 имя такого узла передается в
качестве параметра методу peerRemoved объекта PcerDiscoveryListener. В строке 219
вызывается метод remove объекта Iterator, который удаляет текущую запись из
peerTTLMap. Если измененное значение TTL больше или равно нулю, аренда узла
все еще имеет место, поэтому в строках 224-225 TTL узла уменьшается. Метод
logout (строки 238-242) дает возможность внешнему объекту завершить МпШ-
cas tRecei vingTh read.
Для использования классов MulticastReceivingThread и PeerDiscoveryListener
в Deitel Instant Messenger мы должны были модифицировать клаос PeerList в
нашей реализации Instant Messenger, использующей Jini. Рис. 9.15 содержит
листинг измененного PeerList.
1 // PeerLiat.Java
2 // Начинает рассылку сообщежми,
3 // а также покачает уалы а список ш окне
4 package com.deitel.advjhtpl-p2p;
5
6 // Нааоанй пакет Java
7 import java.awt.*;
8 import java.awt.event.*;
9 import java.net.HalformedURLException;
10 import java.util.*;
11 import java.util.List;
12 import java.io.XOException;
13 import java.rmi.*;
14
15 // Дополшмлыш* пакеты Java
16 import javen.awing.*;
17 import javax.awing.event.*;
18
19 // Пахеты Deitel
20 import com.deitel.advjhtpl-jini.IM.service.ZHServica;
21 import com.deitel.advjhtpl.jini.M.client.IMPeerListener;
22 import com.deitel.advjhtpl.jini.IM.XMPaerlmpl;
23 import com.deitel.advjhtpl.jini.IM.XKPeer;
24
25 public claaa PeerLiat extends JFrame
26 implements PeerDiscoveryListener, ZMConstanta {
27
28 // икнцкалкзаакя аяоюошого польаоаателя
29 private String userName = "anonymous";
30 private HulticastSendingThread multicastSender;
31 private MulticastReceivingThread multicastReceiver;
32
33 // список переменных
34 private DefaultLiatHodel peerNames; // содержит кнеиа уэлоа
35 private List peerStubAddreases; // содержит заглушки уаяоа
36 private JList paerJList;
37
38 // добавление имени узла и заглуши узла ■ список
39 public void peerAdded( String паша. String peerStubAddreas )
40 (
41 // добавление имени в peerNames
42 peerNames.addElement{ name );
43
44 // добавление эаглуши в peerStubAddresses
45 peerStubAddresses.addt peerStubAddreas );
46
47 } // окончание метода peerAdded
48
49 // удаление сервисов из пользовательского интерфейса PeerList
50 // и структуры данных
51 public void peerReraovedt String name )
52 t
53 // удалекие имени и* peerNames
54 int index = peerNames.indexOf( паше );
55 peerNames.removeElamentAt{ index );
56
57 // удаление заглушки из peerStubAddresses
58 peerStubAddresses.remove( index );
59
60 } // окончание метода peecRemoved
61
62 // конструктор
63 public PeerList()
64 t
65 super( "Peer List" );
66
67 // получение инени пользователя
68 userName = JOptionPane.showInputDialog(
69 ' PeerList.this, "Please enter your name: " );
70
71 // изменекие заголовка окна
72 satTitle( userName + "'s Peer List Window" );
73
74 // инициализация списка структур данных
75 peerNames - new DefaultListModel();
76 peerStubAddresses = new ArrayList();
77
78 // инициализация компонентов
79 Container container = getContentPane();
80 peerJList = new JList( peerHames );
81 peerJList.setVie±bleRowCount( 5 );
82 jButton conneetfiutton = new jButton( "Connect" );
83
84 // запрещение множественного выбора
85 peerJList.setSelectionMode(
86 ListSelect±onModel,SlNGLE_SELECTION );
87
88 // задание обработчика событии для connectButton
89 connectButton.sddActionListener(
90 new ActionListenerO {
91
92 public void actionPerformed( ActionEvent event )
Пиринговые приложения и JXTA
94 int itemlndex = peerJList.getSelectedIndex{);
95
96 String stubAddress =
97 ( string ) peerStubAddressee.get( itemlndex );
96
99 // Передача RMI-ссылки в IMService и IMPeer
100 try [
101
102 IMService peerstub =
103 ( IMService ) Naming.lookup{ "rmi://" +
104 stubAddress );
105
106 // настройка пользовательского интерфейса
107 ШРееrListener gui =
108 new IMPeerListenert userName );
109 XMPeerlmpl me =
110 new XMPeerlmpl( userName );
111 roe.addListener( gui );
112
113 // связывание с удаленным объектом IMPeer
114 IMPeer myPeeг = peerStub.connect( me };
115 gui.addPeert myPeer );
116 )
117
11B // обработка исключения
119 catch( MalformednRLException exception ) {
120 JOptionPane. showMessageDialog
121 { null, "Stub address Incorrectly formatted'' );
122 exception.prlntStackTrace {) ,-
123 >
124
125
126 // Удаленный о&ъегт, не связанный с удаленным реестром
127 catch ( NotBoundException notBoundException > {
128 JOptionPane.showMessageDialog
129 { null, "Remote object not present in Registry" );
130 notBoundException.printStaclcTrace (} ;
131 )
132
133 // BoSMomioe возбуждение RamotaException при соединении
134 catch ( RemoteException remoteException } {
135 JOptionPane.showMessageDialog
136 t null, "Couldn't Connect" );
137 remoteException.prlntStackTraceO;
138 )
139
140 ) // окончание метода ActionPerformad
141
142 } // окончание ActionLlatener алонимяого внутреннего класса
143
144 ); // окончание actionListener для connectButton
145
146 // создание меню File
147 JMenu fileMenu * new JMenu( "File" );
148 fileHenu.setMnemonic{ '£" );
149
150 // пужх* мент "About —
151 JHenuItam about Item = пей JMenuItem( "About. . ." ) ;
152 aboutltem.eeOtaajmonict 'A' );
153 aboutIten.addActionLietaner(
154 new AetionListenar() (
155 public void »ctionPerformed( AetionEvent event )
156 (
157 JOptionPane.ehowMeseageDialogt PeerList.thi»,
158 "Deitel In»tant Ma»«anger" ,
159 "About", JOptionPana.PLAIN_MK3SAGE );
160 )
161 }
162 ) ;
163
164 fil«Henu.add( about I tarn );
165
166 // Создание JMenuBar и подсоедмжежке к теку меня File
167 JMenuBar menuBar = new JMenuBar();
168 laenuBar.add ( fileMenu );
169 setJWenuBar( menuBar );
170
171 // событие закрытия окна
172 addWindowLietenext
173
174 new KftndotrAdapterO (
175
176 public void windowClosing( WindowEvant w )
177 (
176 System.out.printlnt "CLOSING WINDOW" );
179
160 // Otntrntme о* сервисов поиска
181 arul ticastSander. logout () ;
182 atulticastKecaiver. logout () ;
183
184 // Объединение потоков
185 try (
186 multicaatSender.joint);
187 multicaatRaceiver.joia();
188 )
189 catch( InterruptedExceptio* interruptedException ) {
190 InterruptedException.printSteckTrace();
191 )
192
193 System.exitt 0 );
194 )
195 )
196 ) ;
197
198 // Ринеиртга компонентов полмова*еиьского интерфейса
199 peerJLiet.aetFixedCellWidtbt 100 );
200 JPanel inpntPanal ■ new JVamelO ;
201 inputPen*l.add( connectButton );
202
203 container.add ( new JScrollPane( pearJLiet ) ,
204 BorderLayout.NORTH );
Пиринговые приложения и JXTA
459
205 container.add( inputPanel, BorderLayout.SOOTS >;
206
207 // инициализация потоков
208 try {
209
210 multicastReceiver =
211 new MulticaatReeeivingThxead( ияегНашв, this );
212 multicastReceiver.«tart(J;
213
214 multicastSender *
215 пек MulticastSendingThread( uaerMame );
216 multicastSender.atartO :
217
216 )
219
220 // Перехват исключений и информирование пользователя об ошибке
221 catch. ( Exception BanagerException ) (
222 JOptxonPane.shoMMeaaa.geDialog( null,
223 "Error initialising MuLticastSendingThread" +
224 "or MulticaetReceivingThread'' > ;
225 managerExeeption.printStackTrace 0;
226 )
227 I
226
229 public static void main( String arga[[ >
230 (
231 PeerList peerlist = new PeerListO;
232 peerli«t.aetSi*e( 100, 170 );
233 peerlist.satVisible( true >;
234 )
235 )
Рис. 9.15. Модифицированный PeerList дает возможность использовать классы
MuftkastReceivlngThread и PeerDiscoveryUstener в Deitel Instant Messenger
Класс PeerList реализует интерфейс PeerDiscoveryListener —Jini-версию
реализации интерфейса ServieeDiscoveryListener. В строках 210—211 создается Multi-
eastRecervingThread, указатель this передается как PeerDiscoveryListener. В
строке 212 этот поток запускается. В строках 214-215 создается MuIticastSending-
Thread для указания имени пользователя, в строке 216 этот поток запускается.
В строках 39-47 реализуется метод peerAdded, который принимает два
параметра типа String: dame и peerStubAddress. Параметр name определяет имя узла.
Параметр peer Stab Address является строковым унифицированным указателем
ресурса, который содержит информацию, необходимую для осуществления вызова
Naming.lookup на удаленном узел. Выеов Naming.looknp получает удаленную
ссылку IMService. В строках 42-45 сохраняется удаленная ссылка IMService.
В строках 51-60 выполняется метод peerRemoved. Затем информация о данном
узле удаляется из peerNames и peerS tub Addresses. В строках 89-141
определяется ActionListener для J Button, который мы используем для соединения узлов.
В строках 96-97 мы получаем peerStnbAddresses выбранного из JList узла.
В строках 102-104 вызывается метод Naming.looknp для получения ссылки на
объект IMService уделенного узла из реестра RMI. Программный код в строках
106-140 работает аналогично предыдущей реализации Deitel Instant Messenger.
В строках 176-194 определяются действия, выполняемые при выборе
пользователем команды закрыть окно PeerList. В строках 180-181 программные потоки
460
Глава 9
прерываются путем вызова метода logout. Метод join в строках 186—187 связывает
каждый поток, блокируя его перед завершением. В строке 193 осуществляет
выход из программы.
9.13. Введение в JXTA
Корпорация Sun Microsystems, Inc. разработала JXTA1 в ответ на
возрастающую популярность пиринговых приложений. Проект JXTA содержит в себе
стандартный, низкоуровневый, независимый от платформы и языка протокол,
который обеспечивает совместимость Р2Р-приложений. Реализация JXTA выполнена
на Java, но разработчики могут работать с JXTA на любом языке
программирования. JXTA обеспечивает основу, на которой разработчики могут построить любое
Р2Р-приложение.
JXTA предназначена для решения следующих задач разработки пиринговых
приложении:
1. Обеспечение без опасн сети /аутентификация. Большие пиринговые сетевые
системы, такие как AOL Instant Messenger и MSN Instant Messenger,
используют центральные серверы при работе пользователей в сеть. Эта
гарантирует в некоторой степени идентификацию пользователей.
2. Обнаружение узлов. Без центрального сервера трудно узнать о присутствии
других узлов в сети. Многоадресная рассылка в том виде, как она
реализована в Jini, не является жизнеспособным решением за пределами
локальной вычислительной сети.
3. Сетевая несовместимость. В настоящее время, каждое популярное
пиринговое приложение использует специфичные протоколов, которые
препятствуют совместимости с другими приложениями. Например, миллионы
пользователей, работающих с AOL Instant Messenger, не могут взаимодействовать
с пользователями Yahoo Instant Messenger. Большинство пользователей
предпочитают пиринговые приложения, имеющие наибольшее число
пользователей.
4. Несовместимость платформ. Разработчики программного обеспечения
должны переписывать инжний уровень своих пиринговых приложений
для каждой платформы, которую они хотели бы поддерживать.
Беспроводные телефоны И другие мобильные устройства обычно имеют
ограниченный выбор пиринговых приложений, если вообще имеют.
JXTA пытается разрешить эти проблемы путем стандартизации
низкоуровневых протоколов, которые управляют пиринговыми приложениями. JXTA
представляет собой скорее общую инфраструктуру, чем инфраструктуру специального
назначения. Поэтому разработчики могут использовать JXTA для реализации
любого типа пиринговых приложения. Так как все пиринговые приложения на
основе JXTA используют идентичные низкоуровневые протоколы, они будут
совместимы друг с другом:.
Сети, построенные на основе протоколов JXTA, состоят из трех базовых типов
объектов: пиринговых узлов/групп пиринговых узлов, объявлений и каналоз/сооб-
щений. Среда выполнения JXTA ассоциирует имя каждого объекта и сетевой адрес
с уникальным 128-битовым идентификатором.
Узел — это объект, использующий протоколы JXTA (рис. 9.16) для
взаимодействия с другими узлами. Каждый узел нуждается в поддержке только некоторых
протоколов, поэтому устройства с низкой производительностью и небольшим объе-
^ о пол ни тельную информацию можно получить на сайте www.jxta.Drg.
Пиринговые приложения и JXTA 461
мом памяти могут работать в сетях JXTA (хотя и с ограниченной
функциональностью). Группы узлов — это логические объединения узлов. В JXTA имеются
только два правила относительно групп узлов:
1. Узлы могут вступать и покидать группы.
2. Групповой администратор, если группа имеет такового, контролирует
доступ к узлам группы.
Все узлы являются частью World Peer Group. Принадлежность к World Peer
Group не предполагает, что каждый узел может обнаружить и общаться с любым
другим узлом сети.
Объявления — это XML-документы, которые выполняют функции,
аналогичные функциям многоадресных пакетов в Jini. Объект в сети JXTA объявляет о себе
для уведомления других о своем существовании, посылая X ML-доку мен ты,
отвечающие спецификациям JXTA.
Протокол
Peer Discovery
Peer Resolver
Peer Information
Peer Membership
Pipe Binding
Endpomt Routing
Функция
Пиринговые узлы используют этог протокол для обнаружения других
объектов в сетях JXTA путем поиска объявлений
Пиринговые узлы, которые помогают поисковому процессу (например,
обладающие более высокой пропускной способностью, способностью
накапливать данные и т.д.), используют этот протокол
Пиринговые узлы получают информацию о других узлах с помощью этого
протокола -
Пиринговые узлы используют этот протокол для изучения требований
групп, например, как вступить в группу, как покинуть группу
Аутентификация и обеспечение безопасности реализованы с помощью
этого протокола.
Пиринговые узлы могут связывать каналы Друге другом с помощью
объявлений, используя этот протокол.
Маршрутизаторы узла реализуют этот протокол для обеспечении сервисов
маршрутизации (например, для туннелирования через межсетевой экран)
Рис. 9.16. Низкоуровневые протоколы JXTA
Каналы представляют ненадежные однонаправленные коммуникационные
связи между узлами. Более сложные каналы могут быть надежными и обеспечивать
передачу данных в нескольких направлениях. Ранее в этой главе мы упоминали,
что ссылка на узел RMI позволяет установить одностороннюю связь с этим узлом.
Каналы функционируют аналогично. Два узла взаимодействуют с помощью двух
каналов, по которым данные передаются в противоположных направлениях. Узлы
взаимодействуют, обмениваясь сообщениями через каналы. JXTA определяет
структуру сообщений. Последние реализации JXTA используют XML-сообщения.
Разработчики JXTA используют XML из-за требований совместимости. Однако
JXTA не ограничивает формат сообщения использованием XML.
JXTA все еще находится в состоянии разработки и до сих пор не все проблемы
пиринговых приложений решены. Средства обнаружения узлов и обеспечения
безопасности продолжают развиваться. JXTA предполагает, что обнаружение
узлов использует комбинацию: механизмов обнаружения, используемых в
локальных вычислительных сетях, с помощью приглашений, каскадного обнаружения
и обнаружение при встрече. Jini иллюстрирует первый нз перечисленных
механизмов. При его использовании узлы в локальной сети находят друг друга
автоматически посредством многоадресной рассылки пакетов. Обнаружение с помощью
Глава 9
приглашений происходит, когда узел получает сообщение от первоначально
неизвестного узла. Каскадное обнаружение — это механизм распределенного поиска,
схожий с механизмом, используемым в Gnutella. Обнаружение при встрече — это
механизм, используемый некоторыми Web-сайтами, которые публикуют адреса
пользователей.
Дополнительную информацию о текущем состоянии JXTA и других
пиринговых технологий можно получить, обратившись к ресурсам, адреса которых
приведены в разделе 9.14.
9.14. Ресурсы в Internet и во Всемирной паутине
www.openp2p.com
шшш.арепр2рлот — это Web-сайт, который является частью O'Reilly Network. На нем
можно найти статьи и ссылки по пиринговым технологиям.
WWW.clip2.COB
Этот сайт предоставляет информацию по популярным и развевающимся пирииговыы
технологиям. Этот сайт также предоставляет материалы, которые объясняют, как
работают разные пиринговые протоколы.
www. рмх- to-pMEwg. org
Эта страница опубликована рабочей группой peer-to-peer.
Это официальный Web-сайт JXTA, который содержит файлы исходного кода, кроме
того можно принять участие в разработке JXTA.
www .p**rint#lliaeiice. сов
Этот Web-сайт публикует материалы, в которых обсуждается, как развиваются
пиринговые технологии. Этот сайт в основном посвящен применению пиринговых
технологий на практике.
Этот сайт предоставляет материалы обсужденва пиринговых технологий.
Резюме
• 8 пиринговых сетях каждый узел сети может выполнять функции как клиента, так и
сервера. Такие сети распределяют обработку информации среди большого числа компыоте-
• Разработчики могут равлизоаывать пиринговые приложения, используя различные
технологии, например, многоадресные сокеты.
• Во многих сетях компьютеры разделены по функциям, которые они выполняют.
• Вместо функционального разделения компьютеров в пиринговых сетях все компьютеры
действуют и как клиенты, и как серверы.
• Концепция пиринговых приложений похожа на концепцию, используемую в телефонии:
пользователь может я говорить (посылать информацию), и слушать (получать ивформа-
• Многие сетевые приложения не могут быть точно отнесены к клиент-серверным либо
пиринговым.
• Недостатком централизованных систем является зависимость от центрального сервера.
Если в центральном узле (сервера) происходит сбой, то же самое происходит во всем
приложении.
• Возможности сервера ограничивают выполнение приложения.
• Централизованные архитектуры упрощают задачи управлении, включая мониторинг
пользовательского доступа.
• Пиринговые приложения децентрализованы и не страдают от недостатков, присущих
прикожанням, которые зависят от центральных серверов.
Пиринговые приложения и JXTA 463
• Некоторые пиринговые приложения используют распределенную вычислительную
мощность компьютеров сети.
• Пиринговые архитектуры позволяют производить поиск в реальном времени, результаты
которого актуальны на время выполнения поиска.
• Поиск в пиринговой сети отражает состояние сети на момент запроса.
• В пиринговой сети трудно определить, кто находится в сети в дан вый момент времени.
• Поиск в реальном времени осуществляется медленно и повышает трафик в сети, потому
что каждый запрос должен пройти через всю сеть.
• Клиент-серверная сеть централизована, тогда как пиринговое приложение децентрализо-
• Многие приложения используют обе архитектуры для достижения специфических целей.
• Обнаружение является действием по нахождению узлов в пиринговых приложениях.
• Децентрализация приложения делает обнаружение узлов н поиск информации затрудни'
тельным.
• Распределенный поиск делают сети более устойчивыми.
• Информация, найденная при распределенном поиске, является актуальной, так как
отражает текущее состояние сети.
Терминология
authentication — аутентификация
bootstrapping — автоматическое приведение
системы в заданное состояние
central server — центральный сервер
centralization — централизация
client/server computing — клиент-серверная
обработка данных
decentralization — децентрализации
distributed search — распределенный поиск
Gnutella, технология
Jini, технология
JXTA, технология
lookup eervice — сервис поиска
multicast socket — многоадресный сокет
Упражнения для самоконтроля
9.1. Заполните пропуски в следующих высказываниях:
a) Чтобы отправить сообщение удаленному узлу, Deitel Instant Messenger должен
получить на этот уделенный узел.
b) Каналы — это однонаправленные коммуникационные связи.
c) Большие пиринговые сетевые приложения, такие как AOL Instant Messenger
и MSN Instant Messenger, используют для аутентификации пользователей.
d) Обнаружение пиринговых узлов является действием по __ и
их с другими узлами.
e) Класс может быть использован для кэширования посредников всех
сервисов, имеющихся в поисковом сервисе.
9.2. Ответьте, является ли каждое из следующих выскзоываний истинным или ложным.
Если высказывание ложно, объясните, почему.
a) В пиринговом приложении каждый узел выполняет функции и клиента, и сервера.
b) Удаленная ссылка обеспечивает однонаправленную связь.
c) Jini это лучший инструмент для проектирования пиринговых приложений.
d) Клиент-серверная архитектура сети является наиболее практичным способом
организации групп компьютеров.
e) Класс ServiceDiscoveryManager нуждается в экспорте классов для асинхронного
уведомления ServiceDiscoveryListener клаоса ServiceDiscoveryEvent.
Р2Р (peer-to-peer) application — пиринговое
приложение, в котором узлы выполняют
функции н клиента, и сервера
peer — пиринговый увел
peer discovery — обнаружение узла
pipe — программный канал
proxy —про грамма, посредник
real-time search — поиск в реальном времени
Remote Method Invocation (RMI), технология
Bcarch engine — механизм поиска,
поисковый сервер
Time To Live (TTL) — время жизни
unicest discovery — одностороннее
обнаружение
464
Глава 9
f) Широковещательная рассылка пакетов — это практичное разрешение проблемы
обнаружения узлов в крупномасштабных пиринговых сетях.
g) Все сетевые приложения относятся ннбо к пиринговым, либо клиент-серверным.
Ответы на упражнения для самоконтроля
9.1. а) ссылку. Ь) ненадежные, с) центральные серверы, d) поиску, связыванию, е) Serrice-
EHscoveryManager.
9.2. а) Истинно.
b) Истинно.
c) Ложно. Реализация аутентификации, обеспечении безопасности и
крупномасштабных приложений с помощью Лш аетруднено. В Jlni нет автоматических механизмов
обнаружения узлов с помощью распределенного поиска.
d) Ложно. В клиент-серверной среде трудно использовать простаивающие
вычислительные мощности и память клиентских компьютеров.
e) Истинно.
f) Ложно. Широковещательная рассылка пакетов ве язляетсв практичным способом
обнаружения компьютеров в крупной сети. При возрастании числа компьютеров в сети
избыточно возрастает трафвк обнаружения, что делает таксе решение непрактичным.
g) Ложно. Многие приложения включают элементы обеих архитектур.
Упражнения
9.3. Измените приложение Deitel Instant Messenger так, чтобы оно могло быть
использовано для пересылки файлов изображений а отображения их на удаленном компьютере.
9.4. Измените приложение Deitel Instant Messenger так, чтобы оно проигрывало
выбранный пользователем звуковой файл при получении сообщение.
9.5. Deitel Instant Messenger может поддержинать несколько одновременных диалогов с
одним и тем же пользоветелем. Измените Deitel Instant Messenger так. чтобы оно не раз-
рещело этого.
9.6. Измените Deitel Instant Messenger так, чтобы узлы уведомляли другие узлы, с
которыми они взаимодействуют о завершении свсеЙ работы. Уделенные узлы должны быть
повторно подключены для продолжения рассылки сообщении.
9.7. Расширьте Deitel Instant Messenger так, чтобы пользователи могли иметь
пользовательский профиль. Дайте возможность пользователям осуществлять поиск других
пользователей с помощью ключеных слов.
ffi
1
785951"800510
Этот файл был взят с сайта
http://all-ebooks.com
Данный файл представлен исключительно в
ознакомительных целях. После ознакомления с
содержанием данного файла Вам следует его
незамедлительно удалить. Сохраняя данный файл
вы несете ответственность в соответствии с
законодательством.
Любое коммерческое и иное использование кроме
предварительного ознакомления запрещено.
Публикация данного документа не преследует за
собой никакой коммерческой выгоды.
Эта книга способствует профессиональному росту
читателей и является рекламой бумажных изданий.
Все авторские права принадлежат их уважаемым владельцам.
Если Вы являетесь автором данной книги и её распространение
ущемляет Ваши авторские права или если Вы хотите
внести изменения в данный документ или опубликовать
новую книгу свяжитесь с нами по email.