Текст
                    Этот файл был взят с сайта
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.